快慢指针最常用的场景就是链表判环了,一快一慢,套圈了就是有环,可以再回顾一下这道经典的题:
141. 环形链表
这道题的思路就是快慢指针,出发后再次相遇的话就是链表有环。直接看代码:
class Solution:
def hasCycle(self, head: ListNode) -> bool:
# 注意判空
if not head: return False
# 初始化快慢指针
slow,fast = head,head
# 循环退出条件一般都是跑的快的那个为空
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
我们继续看入环点的那道题:
142. 环形链表 II
这道题其实我做过好几遍,但是总是忘记那个公式怎么推导,(就是官方题解里边的公式),今天再次做的时候,有一种顿悟的感觉,不需要那么麻烦,从简单情况入手就可以了。还是先画图:
其实就按照最简单的一种情况来说:快指针刚跑了一圈多一点就跟慢指针相遇了。
那么根据快指针走过的路径是慢指针的两倍,我们可以写出a+b+c+b=2(a+b),即a=c,啥意思,相遇的时候,如果一个指针从相遇点出发往前走和另一个指针同时从原点出发往前走,会刚好在入环点相遇,这就是我们要找的入环点位置。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
if not head:return None
start,slow,fast = head,head,head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
# 快慢指针相遇后开始让start跑起来
while slow != start:
start = start.next
slow = slow.next
return slow
return None
继续再来一道:
876. 链表的中间结点
思路还是那样:快指针一次前进两步,慢指针一次前进一步,当快指针到达链表尽头时,慢指针就处于链表的中间位置。(这中间包含了偶数情况,自然就得到的是中间两个点的右边)
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
# 题目说了非空,不判空了
slow,fast = head,head
# 快慢指针,快指针为空的时候,慢指针刚好到中间
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
是不是更简单,再来一道:
19. 删除链表的倒数第 N 个结点
这道题需要有一点脑筋急转弯,就是快慢指针如何在这种场景下应用:
我们的思路还是使用快慢指针,让快指针先走n
步,然后快慢指针开始同速前进。这样当快指针走到链表末尾null
时,慢指针所在的位置就是倒数第n
个链表节点(n
不会超过链表长度)。当然要考虑删除头节点的情况。
代码如下:
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
# 本次解法不用虚拟头节点来解,还是快慢指针
slow,fast = head,head
# 让fast先跑n步
while n:
n -= 1
fast = fast.next
# 停下来先看一下fast有没有到头,到头了说明就是链表长度,这样需要删除第一个元素
if not fast:
return head.next
# 不然就两个一起走,fast走到头,slow.next就是倒数n
while fast.next and fast:
slow = slow.next
fast = fast.next
# 删除slow.next
slow.next = slow.next.next
return head
其实这道题用另一种编码也可以,这是第一次做这道题的时候的想法,这个理解上会稍微复杂一点,因为引入了虚拟头节点,这样中间就没有判断提前返回的情况了,但是理解上会更加复杂一点。
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
emtpy = ListNode()
emtpy.next = head
pre,q = emtpy,head
#先让q找到自己应该在的一个位置,也就是下一步pre刚好跑到要删除节点的上一个节点的时候
#q刚好指向末尾的空指针了,所以q需要比pre提前出发n步
while n:
n -= 1
q = q.next
#q刚好指向末尾的空指针的时候,pre刚好跑到要删除节点的上一个节点
while q:
q = q.next
pre = pre.next
#删除待删除的节点
pre.next = pre.next.next
#返回链表的头节点,因为可能对原先的链表头节点进行操作,需要返回初始虚拟头节点的下一个节点
return emtpy.next