双指针算法(Two Pointers Technique)是算法领域中一种经典且高效的解题思路,特别适合处理线性数据结构中的序列问题。这种算法的核心在于通过维护两个指针(索引)在序列中按特定规律移动,从而将原本O(n²)的时间复杂度优化到O(n)级别。
在实际工程中,双指针算法最常见的应用场景包括:
提示:双指针并非真正的指针实现,在Python等语言中通常用整数索引模拟指针行为,而在C++中可以直接使用指针变量。
单调性(Monotonicity)是指序列元素按照严格递增或递减的规律排列的特性。在双指针算法中利用单调性,可以确保指针移动方向的确定性,避免回溯操作带来的性能损耗。
典型的单调性应用案例:
python复制# 有序数组的两数之和
def two_sum(nums, target):
left, right = 0, len(nums)-1
while left < right:
s = nums[left] + nums[right]
if s == target:
return [left, right]
elif s < target:
left += 1 # 利用递增单调性,左指针只能右移
else:
right -= 1 # 右指针只能左移
当序列具有单调性时,双指针的移动遵循以下黄金法则:
这种策略确保了每个元素最多被访问两次,将时间复杂度从暴力解的O(n²)降至O(n)。
python复制def maxArea(height):
left, right = 0, len(height)-1
max_area = 0
while left < right:
h = min(height[left], height[right])
max_area = max(max_area, h*(right-left))
if height[left] < height[right]:
left += 1 # 保持较高边界不动
else:
right -= 1
return max_area
数学证明:设height[left] < height[right],则所有以left为左边界的情况中,当前组合已取得最大宽度(right-left)。由于高度受限于height[left],移动右指针只会减小宽度而高度不会增加,因此只需移动左指针。
python复制def threeSum(nums):
nums.sort()
res = []
for i in range(len(nums)-2):
if i > 0 and nums[i] == nums[i-1]:
continue
left, right = i+1, len(nums)-1
while left < right:
s = nums[i] + nums[left] + nums[right]
if s < 0:
left += 1
elif s > 0:
right -= 1
else:
res.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
return res
去重技巧:排序后跳过相同元素是关键,时间复杂度O(n²)优于暴力解O(n³)。
python复制def trap(height):
left, right = 0, len(height)-1
left_max = right_max = 0
res = 0
while left < right:
if height[left] < height[right]:
left_max = max(left_max, height[left])
res += left_max - height[left]
left += 1
else:
right_max = max(right_max, height[right])
res += right_max - height[right]
right -= 1
return res
物理意义:双指针从两端向中间移动,每次处理较低的一侧,确保另一侧有更高的边界可以存水。
python复制def minWindow(s, t):
from collections import defaultdict
need = defaultdict(int)
for c in t:
need[c] += 1
left = 0
missing = len(t)
res = ""
for right, c in enumerate(s):
if need[c] > 0:
missing -= 1
need[c] -= 1
if missing == 0:
while left <= right and need[s[left]] < 0:
need[s[left]] += 1
left += 1
if not res or right-left+1 < len(res):
res = s[left:right+1]
need[s[left]] += 1
missing += 1
left += 1
return res
窗口维护:右指针扩展窗口直到满足条件,左指针收缩窗口优化解,时间复杂度O(n)。
python复制def minSubArrayLen(target, nums):
left = total = 0
res = float('inf')
for right in range(len(nums)):
total += nums[right]
while total >= target:
res = min(res, right-left+1)
total -= nums[left]
left += 1
return res if res != float('inf') else 0
优化点:当数组元素全为正数时,滑动窗口的和具有单调性,确保算法正确性。
python复制def hasCycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
数学原理:快慢指针相遇时,快指针比慢指针多走一圈,时间复杂度O(n)。
python复制def middleNode(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
应用场景:在不知道链表长度的情况下,单次遍历找到中间节点。
常见错误包括:
正确实践:
python复制while left < right: # 标准双指针条件
if need_move_left:
left += 1
# 检查越界和重复
while left < right and nums[left] == nums[left-1]:
left += 1
else:
right -= 1
while left < right and nums[right] == nums[right+1]:
right -= 1
当处理大数计算时(如乘积类问题),需要考虑:
python复制# 错误示例
if nums[left] * nums[right] > target: # 可能溢出
# 正确做法
if nums[left] > target / nums[right]: # 避免溢出
在某些问题中可以提前结束循环:
python复制# 两数之和已排序数组
while left < right:
s = nums[left] + nums[right]
if s == target:
return [left, right]
elif s < target:
left += 1
else:
right -= 1
# 当最小和大于目标时可提前终止
if nums[left] + nums[left+1] > target:
break
对于特定模式的问题(如删除重复元素):
python复制def removeDuplicates(nums):
if not nums:
return 0
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
处理时间序列日志时,统计特定时间窗口内的请求数:
python复制def count_requests(logs, window):
logs.sort()
left = count = max_count = 0
for right in range(len(logs)):
while logs[right] - logs[left] > window:
left += 1
count = right - left + 1
max_count = max(max_count, count)
return max_count
实现简单的diff工具时,双指针可以高效定位差异位置:
python复制def find_diff(text1, text2):
i = j = 0
while i < len(text1) and j < len(text2):
if text1[i] == text2[j]:
i += 1
j += 1
else:
# 处理差异逻辑
break
return (i, j)
经验分享:在实现双指针算法时,建议先用小规模测试用例手动模拟指针移动过程,确保边界条件处理正确。我曾在解决"接雨水"问题时,因未正确处理等高柱子的情况导致计算结果偏差30%。