双指针之快慢指针

快慢指针最常用的场景就是链表判环了,一快一慢,套圈了就是有环,可以再回顾一下这道经典的题:

141. 环形链表

image-20211021172921635

这道题的思路就是快慢指针,出发后再次相遇的话就是链表有环。直接看代码:

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

image-20211021173336729

这道题其实我做过好几遍,但是总是忘记那个公式怎么推导,(就是官方题解里边的公式),今天再次做的时候,有一种顿悟的感觉,不需要那么麻烦,从简单情况入手就可以了。还是先画图:

image-20211021174026059

其实就按照最简单的一种情况来说:快指针刚跑了一圈多一点就跟慢指针相遇了

那么根据快指针走过的路径是慢指针的两倍,我们可以写出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. 链表的中间结点

image-20211021174547578

思路还是那样:快指针一次前进两步,慢指针一次前进一步,当快指针到达链表尽头时,慢指针就处于链表的中间位置。(这中间包含了偶数情况,自然就得到的是中间两个点的右边)

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 个结点

image-20211021174839638

这道题需要有一点脑筋急转弯,就是快慢指针如何在这种场景下应用:

我们的思路还是使用快慢指针,让快指针先走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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值