1.基础概念
两个指针有 n*n种组合,因此时间复杂度是 O(n^2) 。而双指针算法就是运用单调性使得指针只能单向移动,因此总的时间复杂度只有 O(2n),也就是O(n)。双指针可以分为两种类型,一种是快慢指针,一种是对撞指针。
2.快慢指针
快慢指针是指定义快指针fast,慢指针slow,两个指针以不同的步长向前移动,直到两个指针的值相等或者满足条件为止。使用快慢指针可以解决如:
(1)leetcode141环形链表是否存在环
(2)剑指offer022链表中环的入口节点
(3)剑指offer022删除链表中的倒数第K个元素
3.对撞指针
对撞指针又叫左右指针,定义左指针left,右指针right,从两端遍历直到两指针相遇或满足条件为止。以下这些题目均使用了对撞指针的思想。
(1)leetcode7.整数反转
(2)leetcode9.回文数
(3)leetcode27.移除元素
(4)leetcode125.验证回文串
(5)leetcode167.两数之II-输入有序数组()
(6)leetcode190.颠倒二进制位()
(7)leetcode344.反转字符串()
(8)leetcode345.反转字符串中的元音字母()
(9)leetcode11.盛水最多的容器(medium)
4.快慢指针举例
leetcode141 环形链表:
具体分析如下图:
代码如下:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
fast=head
slow=head
if not head: #链表为空的特殊情况
return False
while fast and fast.next: #在没有环的情况下,快指针先走完
fast=fast.next.next
slow=slow.next
if fast==slow:
return True
return False
leetcode142 环形链表2:141题中判断链表是否为环形链表,只需要快慢指针是否相遇。进一步思考,如何判断链表是从哪个节点进入环的?
(1)跟上一题相同,仍然设置fast和slow指针,slow指针第一次进入环并且在遍历完环之前快慢指针一定会相遇,原因如下图:
(2)将链表分成如下图三部分,
第一次相遇时slow走过的路程是s=a+b,
fast走过的路程是f=a+n(b+c)+b, n为快指针比慢指针多走的圈数,f=2s,所以a+n(b+c )+b=2(a+b),化简后a+b=n(b+c)第一次相遇时n=1,所以a=c
(3)代码如下:
# 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]:
slow=head #快慢指针
fast=head
while fast and fast.next:
slow=slow.next
fast=fast.next.next
if slow==fast:#相遇时:新指针从链表头部出发,slow沿原路线走
a=head
while a!=slow:
a=a.next
slow=slow.next
return a #返回a或者slowslow均可
return None
5.对撞指针举例
对撞指针leetcode977
该题解法最容易想到的是先对数组每个元素求平方值再进行排序,写法如下:
思路是正确的但是时间复杂度过高,超出题目要求时间。
仔细观察该题目,该数组已经排序,平方后是两边到中间数值逐渐减小,可以使用左右指针比较两侧的数据,能减少时间复杂度。写法如下:
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
n=len(nums)
res=[-1]*n
#左右指针从两头开始遍历
left=0
right=n-1
k=n-1
#当两指针相遇时停止循环
while right>=left:
if nums[left]**2>nums[right]**2:
res[k]=nums[left]**2
left+=1
else:
res[k]=nums[right]**2
right-=1
k-=1
return res
滑动窗和双指针的概念类似,双指针更注重两个指针的值,而滑动窗口更关注窗口这个区间的值。如leetcode209 长度最小的字符串,给定一个数组和target值,求出和为target的最小连续字数组。
我们很容易就能想到该题目思路:
我们通常使用循环来实现遍历所有元素的目的,那么start和end该怎么参与到循环里?整个程序结束的条件应该是遍历了所有的子数组,end和start都可以循环到数组最后一个元素时实现遍历所有子数组。但是可以看出end能更先到达最后一个值,此时start-end区间的窗口缩小,改变start位置即可实现。具体代码如下:
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
n=len(nums)
start,end=0,0
s=0
list_len=n+1
while end < n:
s+=nums[end]
while s >= target:
list_len=min(list_len,end-start+1)
s-=nums[start]
start+=1
end+=1
return 0 if list_len==n+1 else list_len