欢迎关注我的CSDN:https://spike.blog.csdn.net/
本文地址:https://spike.blog.csdn.net/article/details/139270999
双指针算法是一种常见且灵活的技巧,通过使用两个指针协同完成任务。这些指针可以指向不同的元素,具体应用取决于问题的性质。双指针算法的常见用法:
- 对撞指针:一左一右向中间逼近。例如,反转字符串中的元音字母问题,可以使用对撞指针。
- 快慢指针:一快一慢,步长不同。例如,判断链表中是否有环问题,可以使用快慢指针,看慢指针是否能追上快指针。单链表找中间节点问题也可以用快慢指针,快指针到链表结尾,慢指针到一半。
- 滑动窗口:类似计算机网络中的滑动窗口。一般是右端向右扩充,达到停止条件后右端不动,左端向右端逼近,逼近达到停止条件后,左端不动,右端继续扩充。
双指针算法包括:对撞指针、快慢指针、滑动窗口、双链遍历。
- 167. 两数之和 II - 输入有序数组 - 对撞指针
- 633. 平方数之和 - 对撞指针,题目167变种
- 680. 验证回文串 II - 对撞指针,需要判断去除1个字符 l+1 或 r-1
- 15. 三数之和 - 两数之和的进阶版,先排序,逐步修改序列
- 142. 环形链表 II - 快慢指针
- 76. 最小覆盖子串 - 滑动窗口,Counter类的使用
- 88. 合并两个有序数组 - 双链遍历
- 524. 通过删除字母匹配到字典里最长单词 - 双链遍历,优先排序,再依次比较。
1. 对撞指针
167. 两数之和 II - 输入有序数组 - 对撞指针
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
"""
时间复杂度O(n),空间复杂度O(1)
"""
# 输入的已经排序
n=len(numbers) # 数量
l,r=0,n-1 # 左右指针
while l<r:
v=numbers[l]+numbers[r] # 两数之和
if v>target: # 移动指针
r-=1
elif v<target:
l+=1
else:
return [l+1,r+1]
633. 平方数之和 - 对撞指针,167变种
class Solution:
def judgeSquareSum(self, c: int) -> bool:
"""
时间复杂度 O(n),空间复杂度 O(1)
"""
# 左右指针
l, r = 0, int(sqrt(c))
while l <= r: # 遍历条件需要相等
# 计算值
v = pow(l, 2) + pow(r, 2)
if v > c: # 移动右指针
r -= 1
elif v < c: # 移动左指针
l += 1
else:
return True
return False
680. 验证回文串 II - 对撞指针,需要判断去除1个字符 l+1 或 r-1
class Solution:
def validPalindrome(self, s: str) -> bool:
"""
时间复杂度 O(n), 空间复杂度 O(1)
"""
def check(l, r):
"""
检查s是否是回文
"""
while l < r:
if s[l] == s[r]:
l += 1
r -= 1
else:
return False
return True
n = len(s)
l, r = 0, n-1 # 左右指针
while l < r:
if s[l] == s[r]: # 回文
l += 1
r -= 1
else:
# 越过1个值,最多可以从中删除一个字符
return check(l+1, r) or check(l, r-1)
return True
15. 三数之和 - 两数之和的进阶版,先排序,逐步修改序列
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
"""
时间O(N^2),空间O(logN) -> 排序
"""
def two_sum(nums, t):
"""
经典的两数之和,同时,避免重复添加
"""
n = len(nums)
l, r = 0, n-1
res = []
while l < r:
x = nums[l] + nums[r]
if x > t:
r -= 1
elif x < t:
l += 1
else:
sr = [nums[l], nums[r], -t]
if sr not in res: # 避免重复添加
res.append(sr)
l += 1
return res
nums.sort() # 排序
n = len(nums) # 序列长度
res = [] # 输出结果
for i in range(n):
# 避免重复数字
if i > 0 and nums[i-1] == nums[i]:
continue
t = nums[i] # 依次遍历
res += two_sum(nums[i+1:], -t)
return res
2. 快慢指针
142. 环形链表 II - 快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
"""
时间复杂度O(n),空间复杂度O(1)
"""
if not head:
return None
# 快慢指针先指向head
slow=fast=head
# 判断是否运行到结尾
while fast.next and fast.next.next:
slow=slow.next # 移动1步
fast=fast.next.next # 移动2步
if slow==fast:
fast=head # fast从head开始重新计数
while slow!=fast: # 移动到位置
slow=slow.next
fast=fast.next
return fast # 相等即返回
return None
3. 滑动窗口
76. 最小覆盖子串 - 滑动窗口
class Solution:
def minWindow(self, s: str, t: str) -> str:
"""
时间复杂度 O(m+n),空间复杂度 O(1)
"""
rl,rr=-1,len(s) # 目标的最大窗口
l=0 # 左指针
cnt_s=Counter() # 计数器字典
cnt_t=Counter(t) # 目标计数器字典
less=len(cnt_t) # 不重复的t字母数量
for r,c in enumerate(s):
cnt_s[c]+=1 # s计数器
if cnt_s[c]==cnt_t[c]: # 数量相等
less-=1 # 数量减一
while less==0: # 满足条件
if r-l < rr-rl: # 区间更小
rl,rr=l,r # 更新左右指针
# -----
# 如果数量相等,之后数量需要减一,所以less需要提前+1
x=s[l] # 左指针当前字母
if cnt_s[x]==cnt_t[x]:
less+=1 # 不重复+1
cnt_s[x]-=1 # 指针减1
l+=1 # 已经更新,所以需要移动左指针
# -----
return "" if rl<0 else s[rl:rr+1]
4. 双链遍历
88. 合并两个有序数组 - 双链指针
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
时间复杂度 O(n),空间复杂度 O(1)
"""
pos=(m-1)+(n-1)+1 # 最远位置
m-=1 # 最后位置
n-=1 # 最后位置
while m>=0 and n>=0: # 遍历两个数组
# 注意: 大于等于确保,优先移动m
if nums1[m]>=nums2[n]:
nums1[pos]=nums1[m] # 赋值大值
m-=1
else:
nums1[pos]=nums2[n]
n-=1
pos-=1 # 移动指针
while n>=0: # 优先移动m,所以剩下的就是n
nums1[pos]=nums2[n]
pos-=1
n-=1
524. 通过删除字母匹配到字典里最长单词 - 双链指针,优先排序,再依次比较。
class Solution:
def findLongestWord(self, s: str, dictionary: List[str]) -> str:
"""
d 是字典长度,m 是s长度
时间复杂度 O(d x (m+n)),空间复杂度 O(d x m)
"""
# 按目标结果排序,优先做前面的
words = sorted(dictionary, key=lambda x: (-len(x), x))
for t in words:
i = j = 0 # 双指针
while i < len(t) and j < len(s): # 双指针遍历
if t[i] == s[j]: # 相同
i += 1 # t指针+1,满足条件
j+=1 # s指针默认都+1
if i == len(t): # 长度满足
return t # 直接返回
return ""