Leetcode刷题笔记之: 链表(双指针技巧)

链表的双指针技巧

  1. 环形链表 I
    单链表中的双指针技巧通常是快慢指针。即设置一快一慢的指针。
    对于此题,判断链表中是否有环。我们设置快慢指针,如果有环,我们发现快指针会首先进入环的循环,然后迟早与慢指针相遇。如果无欢,快指针则会在与慢指针相遇前便到达尾结点。
    计算复杂度: O(N)
    空间复杂度: O(1)
class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        if not head or not head.next:
            return False
        # 设置快慢指针
        slow = head
        fast = head.next
        # 让慢指针追逐快指针直至相遇或者快指针慢指针到头
        while fast:
            # 相遇
            if slow == fast:
                return True
            # 到头
            if not fast.next:
                return False
            # 指针前移
            slow = slow.next
            fast = fast.next.next
        return False
  1. 环形链表 II
    这一题与上一题不同的点在于,我们不仅需要找到图中是否有环,还需要找到确切的分叉点。上一题中我们知道只要有环,快慢指针就一定会相遇,但是相遇点却不一定是分叉点,那我们可以尝试计算一下分叉点的位置。
    假设每次循环slow指针走一步,fast走两步,设链表从开头到分叉点的距离为a,环的长度为b。那第一次相遇时,fast指针一定是比slow多走了n个环的长度的。所以我们有:
    f a s t = 2 × s l o w f a s t = s l o w + n b n = 0 , 1 , 2 , 3... fast = 2\times slow \\ fast = slow + nb \\ n = 0,1,2,3... fast=2×slowfast=slow+nbn=0,1,2,3...
    f a s t = 2 n b , s l o w = n b fast = 2nb, slow = nb fast=2nb,slow=nb

那我们就可以将fast指回head,并且以slow一样的速度前进,最终相遇时两者:
f a s t = a s l o w = n b + a fast = a\\ slow = nb + a fast=aslow=nb+a
就可以很方便得出a的长度,即环的分叉口

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return 
        # 快慢指针直到第一次相遇
        slow, fast = head, head
        while True:
            # 如果陷入末尾则无环
            if not fast or not fast.next:
                return 
            # fast 走两步,slow 走一步
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                break
        # 第一次相遇后,slow走了nb,fast走了a + nb
        # fast置于head,每次走一步,经过a步便可以与slow相遇与交点
        fast = head
        while slow != fast:
            fast = fast.next
            slow = slow.next
        return fast
  1. 相交链表
    给定两个长短不一的链表,他们有着共同的尾部,我们需要求尾部的起点,即他们的交点。
    首先很明显,我们仍然使用双指针技巧,每个链表都被交点分成了两个部分,我们不妨设第一个链表长度为a + c,第二个为 b + c,c是尾部的长度。
    判断是否是交点的条件便是上下指针第一次重合,但是如果都从头出发,除非a == b,不然由于从头到交点的路程不一样,他们只会先后到达交点。所以我们需要使得上下两个指针的起点到交点的距离相等。
    所以思路明确,假设 a > b,我们可以让a的指针向前进 (b-a)步,以此作为起点,这样他们便可以前进相遇在交点了。
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        if not headA or not headB:
            return 
        # 首先计算两个链表的长度 a + c , b + c
        count_a = 0
        count_b = 0
        cur_a = headA
        cur_b = headB
        while cur_a.next or cur_b.next:
            if cur_a.next:
                cur_a = cur_a.next
                count_a += 1
            if cur_b.next:
                cur_b = cur_b.next
                count_b += 1
        

        # 更长的那一条链表的指针先行
        gap = count_a - count_b
        cur_a = headA
        cur_b = headB
        if gap <= 0:
            for _ in range(-gap):
                cur_b = cur_b.next
        else:
            for _ in range(gap):
                cur_a = cur_a.next
        
        # 起点相同,最终相遇在交点,如果未相遇,则两条链表一定没有相交
        while cur_a and cur_b:
            if cur_a == cur_b:
                return cur_a
            cur_a = cur_a.next
            cur_b = cur_b.next
        return
  1. 删除链表的倒数第N个节点
    依然是双指针,设置一前一后的指针,右边的指针触底时,左边的指针的next便是我们要删除的结点
class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        if not head:
            return
        # 设置双指针,left起始位置为head, right起始位置为left后n位
        dummy = ListNode(0, head)
        left = dummy
        right = head
        for _ in range(n-1):
            right = right.next
        # 向右直至right触底
        while right.next:
            left = left.next
            right = right.next
        # 删除倒数第n个点
        # left即倒数第n+1个点,其next指向倒数第n-1个点
        left.next = left.next.next
        return dummy.next

双指针总结

  1. 调用next字段之前,一定要检查结点是否为空
  2. 循环的结束条件一定要明确
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值