双指针运用(持续更新中…)
0. 简介
双指针法也叫快慢指针,在数组里是用两个整型值代表下标,在链表里是两个指针(python似乎只能用.next
的个数表示不同步长),双指针法一般时间复杂度能达到O(n)。两个指针的位置一般有两种:
- 两个指针以不同的速度(或起始时间)往相同方向移动
- 两个指针分别从前和末尾相向移动
但两种形式基本都是一个指针(快指针)在前“探路”,另一指针(慢指针)在符合某种条件时移动一定步长。接下来是LeetCode中的一些具体的题目
01. 环形链表I/II
141.环形链表
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。142.环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。说明:不允许修改给定的链表。
1.1 环形链表Ⅰ
这相当于是142.环形链表 II
的入门题,起始只需要把握一个点即可:即两个不同速度的指针在有环的链表中终会相遇。直接上代码
class Solution:
def hasCycle(self, head: ListNode) -> bool:
"""
(1)复杂度:
- 时间复杂度:O(N)
- 空间复杂度:O(1)
"""
# 处理特殊情况
if head is None or head.next is None:
return False
# 定义和初始化两个指针(快慢)位于链表起点,快指针领先一个身位
quick, slow = head.next, head
# 如果quick指针追上了slow指针,说明此链表有环,否则没
while quick != slow:
# 如果quick指针提前达到了末尾,说明肯定不是有环的
if quick is None or quick.next is None:
return False
# 如果不是达到末尾,那么quick和slow指针以不同的速度遍历
slow = slow.next
quick = quick.next.next
return True
1.2 环形链表 Ⅱ
同样以双指针来思考,假设两指针 fast
,slow
从链表头部 head
开始移动,fast
步长为2,slow
步长为1,那么对于一个链表存在这样两种情况:
-
链表无环:
fast
指针走到链表末端,即:fast.next is None
,直接返回 -
链表有环:
fast
指针和slow
指针一定会相遇在环内,因为fast
指针到达环内后会一直在环内循环。那么此时两个指针走过的步数就会存在如下关系( 设链表共有x+y
个节点,其中链表头部到链表入口处有x
个节点,链表入口节点不计,链表环有y
个节点,两指针分别走了f
,s
步 ):fast
走的步数是slow
步数的 2 倍,即f = 2*s
(因为fast
指针的步长是slow
的 2 倍)fast
比slow
多走了 n 个环的长度,即f = s + n*y
( 因为 两个指针都走过 链表头到入口节点的x
步,然后在环内绕圈直到重合,重合时fast
比slow
多走 环的长度整数倍 )
重点结论一:以上两式相减得:
f = 2ny,s = ny
,即fast
和slow
指针分别走了 2n,n 个环的周长
假设链表头部为A,环入口为B,相遇为C,那么根据上面的推导,即有:
A
B
+
B
C
=
s
=
n
y
AB+BC= s = ny
AB+BC=s=ny
A B + B C + C B + B C = f = 2 n y AB+BC+CB+BC=f=2ny AB+BC+CB+BC=f=2ny
根据公式(1)和(2)则可以得到:
B C + C B = A B + B C BC+CB=AB+BC BC+CB=AB+BC
A B = C B = x AB = CB=x AB=CB=x
注意:BC和CB代表方向不同
重点结论二:slow
指针从第一次相遇再次走到入口处刚好走了x
的路程
那么根据重点结论二,我们可以让第一次相遇后的快指针回到链表头部,然后两者以相同的速度前进,那么下一次的相遇地点则为环链表的入口。代码如下:
class Solution(object):
def detectCycle(self, head):
"""
(1)复杂度:
- 时间复杂度:O(N)
- 空间复杂度:O(1)
"""
# 处理特殊情况
if head is None or head.next is None:
return None
# 快慢指针指向链表头部
fast, slow = head, head
while True:
if not (fast and fast.next):
return None
# .next.next代表步长为2,.next代表步长为1
fast = fast.next.next
slow = slow.next
# 第一次相遇后需要将快指针指向链表头部
if fast == slow:
break
# 快指针指向链表头部
fast = head
# 再次让两个指针以相同速度前进,下次相遇的位置即为链表入口
while fast != slow:
fast = fast.next
slow = slow.next
return fast