常见的双指针解题思路有以下几种:
1. 固定一个指针,移动另一个指针
- 常用于求解数组或链表中的某种特定条件,比如两个元素的和等于某个目标值。
- 例如,在有序数组中求两个数的和为目标值,可以让一个指针指向数组的开始位置,另一个指针指向数组的末尾位置,然后根据和的大小调整指针。
例子:
def twoSum(nums, target):
left, right = 0, len(nums) - 1
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return [left, right]
elif current_sum < target:
left += 1
else:
right -= 1
return None
时间复杂度:O(n)
2. 左右指针同时移动
- 有时需要通过两个指针来同时扫描某个结构,解题时两指针会一起向前推进,直到满足某个条件。
- 这种方法常常用于删除数组中的重复元素或归并两个有序数组。
例子:删除排序数组中的重复元素
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
时间复杂度:O(n)
3. 快慢指针
- 快慢指针是一种特别的双指针技巧,其中一个指针以两倍速前进,另一个指针以常规速度前进。这种技巧常用于链表中的循环检测或找链表的中间节点等问题。
- 通过这种方法,可以在一个链表中有效地找到环的入口点或检测是否存在环。
例子:链表的环检测
def hasCycle(head):
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
时间复杂度:O(n)
4. 滑动窗口
- 滑动窗口的基本思想是使用两个指针(左指针和右指针),通过调整窗口大小来满足某种条件。
- 它通常用于处理数组或字符串的子串问题,比如最长不重复子串、子数组和等问题。
例子:最长无重复字符子串
def lengthOfLongestSubstring(s):
char_map = {}
left = 0
max_len = 0
for right in range(len(s)):
if s[right] in char_map and char_map[s[right]] >= left:
left = char_map[s[right]] + 1
char_map[s[right]] = right
max_len = max(max_len, right - left + 1)
return max_len
时间复杂度:O(n)
5. 双指针与递归结合
- 双指针方法也可以和递归结合起来使用,特别是处理分治类问题时。
例子:归并排序中的双指针
def merge(left, right):
result = []
i, j = 0, 0
while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
总结
- 双指针的优势:可以在很多情况下减少暴力破解的复杂度,通常能够将时间复杂度降低至 O(n)。
- 适用场景:排序数组、链表、字符串等数据结构中,尤其是当问题涉及查找、比较、合并、去重时。