文章目录
- 双指针算法概述
- 主要应用场景及实现思路
- 1. 数组排序后的两数之和
- 2. 链表中环的检测
- 3. 滑动窗口
- 4. 合并两个有序数组
- 5. 移除数组中的重复元素
- 总结
双指针算法概述
原理:
双指针算法的核心在于使用两个指针遍历数据结构,而不是单个指针。这种方法在某些情况下可以显著减少时间复杂度,特别是在处理数组和链表时。
主要应用场景及实现思路
1. 数组排序后的两数之和
应用场景:
- 在已排序的数组中查找两个数,使它们的和等于特定的目标值。
- 适用于需要高效查找特定组合的情况。
实现思路:
- 初始化两个指针,一个指向数组的起始位置(左指针),另一个指向数组的末尾位置(右指针)。
- 计算两个指针所指向元素的和。
- 根据和与目标值的比较结果,调整指针位置:
- 如果和小于目标值,移动左指针向右。
- 如果和大于目标值,移动右指针向左。
- 如果和等于目标值,返回指针的位置。
- 继续调整指针位置,直到找到答案或两个指针相遇。
代码示例:
def two_sum_sorted(arr, target):left, right = 0, len(arr) - 1while left < right:current_sum = arr[left] + arr[right]if current_sum == target:return [left, right]elif current_sum < target:left += 1else:right -= 1return []# 测试
arr = [1, 2, 3, 4, 6]
target = 10
print(two_sum_sorted(arr, target)) # 输出: [3, 4]
2. 链表中环的检测
应用场景:
- 判断链表中是否存在环。
- 适用于防止无限循环和确保链表结构的正确性。
实现思路:
- 使用两个指针,一个慢指针每次前进一步,一个快指针每次前进两步。
- 如果链表中存在环,快指针最终会追上慢指针。
- 如果快指针到达链表末尾(即
fast
或fast.next
为None
),则说明链表中没有环。
代码示例:
class ListNode:def __init__(self, x):self.val = xself.next = Nonedef has_cycle(head):if not head or not head.next:return Falseslow = headfast = head.nextwhile fast and fast.next:if slow == fast:return Trueslow = slow.nextfast = fast.next.nextreturn False# 测试
# 创建一个带有环的链表
node1 = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)
node4 = ListNode(4)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node2 # 创建环print(has_cycle(node1)) # 输出: True
3. 滑动窗口
应用场景:
- 解决字符串或数组中子串/子数组的问题。
- 特别适用于需要找到满足某些条件的最小子数组长度或最大和等情况。
实现思路:
- 使用两个指针作为窗口的两端,根据条件调整窗口大小。
- 当窗口满足条件时,尝试缩小窗口以寻找更优解。
- 通过不断调整窗口的位置和大小,直到找到满足条件的最佳解。
代码示例:
from collections import Counterdef min_window(s, t):need = Counter(t)missing = len(t)left, start, end = 0, 0, 0for right, char in enumerate(s):if need[char] > 0:missing -= 1need[char] -= 1if missing == 0:while left < right and need[s[left]] < 0:need[s[left]] += 1left += 1if end == 0 or right - left < end - start:start, end = left, right + 1need[s[left]] += 1missing += 1left += 1return s[start:end]# 测试
s = "ADOBECODEBANC"
t = "ABC"
print(min_window(s, t)) # 输出: "BANC"
4. 合并两个有序数组
应用场景:
- 将两个已排序的数组合并成一个新的有序数组。
- 适用于需要高效合并多个有序数据集的情况。
实现思路:
- 分别为两个数组设置指针,从头开始比较两个数组的当前元素。
- 将较小的元素放入结果数组中,并移动对应的指针。
- 继续这一过程,直到其中一个数组的所有元素都被处理完毕。
- 最后,如果还有剩余未处理的数组,则将其余部分直接复制到结果数组的末端。
代码示例:
def merge_sorted_arrays(arr1, arr2):merged = []i, j = 0, 0while i < len(arr1) and j < len(arr2):if arr1[i] < arr2[j]:merged.append(arr1[i])i += 1else:merged.append(arr2[j])j += 1# 添加剩余的元素while i < len(arr1):merged.append(arr1[i])i += 1while j < len(arr2):merged.append(arr2[j])j += 1return merged# 测试
arr1 = [1, 3, 5]
arr2 = [2, 4, 6]
print(merge_sorted_arrays(arr1, arr2)) # 输出: [1, 2, 3, 4, 5, 6]
5. 移除数组中的重复元素
应用场景:
- 在非递减数组中去除重复出现的元素,使每个元素只出现一次。
- 适用于需要高效去重的情况。
实现思路:
- 使用两个指针,一个用于追踪当前处理到的非重复元素的位置,另一个用于遍历整个数组。
- 遍历过程中,如果发现当前元素与前一个元素不同,则将其放置在追踪指针的位置,并向前移动追踪指针。
- 遍历结束后,追踪指针之前的数组部分就是去重后的数组。
代码示例:
def remove_duplicates(arr):if not arr:return 0write_pointer = 1 # 写指针,记录下一个非重复元素的位置for read_pointer in range(1, len(arr)):if arr[read_pointer] != arr[read_pointer - 1]:arr[write_pointer] = arr[read_pointer]write_pointer += 1return write_pointer# 测试
arr = [1, 1, 2, 2, 3, 4, 4, 5]
length = remove_duplicates(arr)
print(arr[:length]) # 输出: [1, 2, 3, 4, 5]
总结
双指针算法通过巧妙地利用两个指针来遍历数据结构,能够在多种场景下提供高效的解决方案。以下是双指针算法的一些关键点:
- 时间复杂度:通常可以将时间复杂度从 O(n^2) 降低到 O(n),显著提高效率。
- 空间复杂度:大多数情况下,双指针算法的空间复杂度为 O(1),因为不需要额外的存储空间。
- 灵活性:适用于多种数据结构和问题类型,包括数组、链表、字符串等。
希望这些详细的讲解和代码示例能帮助你更好地理解和应用双指针算法。如果你有任何进一步的问题或需要更多的示例,请随时告诉我。