LeetCode-142-环形链表 II


题意描述:

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

进阶:

你是否可以不用额外空间解决此题?


示例:

  • 示例 1
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。

在这里插入图片描述

  • 示例 2
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。

在这里插入图片描述

  • 示例 3
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。

在这里插入图片描述


解题思路:
Alice: 这道题是 141-环形链表的增强版啊,不过还是不经打呀。
Bob: 对,这道题不仅仅问了是不是有环,还问了环开始的位置。不过你说不经打是你已经有解法了吗 ?
Alice: 你傻呀 ?还是第一种解法啊,打个标记呗,第一个重复访问的节点就是环的首节点啊。
Bob: 对啊,你好棒啊。那进阶怎么办,你是否可以不用额外空间解决此题?
Alice: 你说怎么办 ?😎
Bob: 还得从双指针的办法入手,双指针只能保证最终在环上的某个节点相遇并不能保证在首节点相遇,得想个办法再把首节点确定。
Alice: 有道理,现在能够知道的有一个链表,链表中环上的某个节点。问题是如何确定首个在环中的节点。
Bob: 啊啊啊啊,我有办法了,从 head 开始往后找,第一个出现在环上的节点就是答案。
Alice: 哦豁,那怎么判断一个节点是不是在环上呢 ?等等,我知道了,遍历整个环就好了,一个一个的检查。
Bob: 对呀,不过这样就写成了二重循环了,也就是 O(n^2)的时间复杂度,不过内存倒是满足进阶的要求了。
Alice: 先码出来再说,说不定有更好的计算,遍历整个环的话明显是在重复计算了。
Bob: 好~

---------------------------------------------------------------------- Alice&Bob -----------------------------------------------------------------------------

Alice: 我明白了,需要定量证明一下。我先简述一下,先说结论,快慢两个指针以 1倍 和2倍 的速度 同时从 head 出发,在环上第一次相遇后,相遇的节点到环的入口的距离和 head 到环的入口的距离相等。
Bob:我去把别人的图拿来用一下吧,直接说不太好懂。
在这里插入图片描述
Alice: distance(slow) * 2 == distance(fast) => 2 * (F + a) = F + a + b + a => F = b
Bob: 哇,这个真是最好的解法了。我居然没有仔细考虑距离的问题,如果距离是成倍的就能用双指针确定环开始的位置。
Alice: 👱‍♀️


代码:
Python 方法一: 使用 set 标记访问过的节点。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        nodeAddress = set()
        while head != None:
            if head in nodeAddress:
                return head
            nodeAddress.add(head)
            head = head.next
        return None

Java 方法一: 使用 HashSet

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        HashSet nodeAddress = new HashSet();
        while (head != null){
            if(nodeAddress.contains(head)){
                return head;
            }
            nodeAddress.add(head);
            head = head.next;
        }
        return null; 
    }
}

Python 方法二: 双指针 + 一个判断

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:

        cycleNode = self.wing(head)
        # cycleNode 为环中任一节点,也可能是首节点,也可能不是。
        if cycleNode == None:
            return None
        else:
            # 寻找环中的首节点
            while head != cycleNode:
                # 从head开始尝试,环中的节点肯定在环中,从head往后找第一个出现在环中的节点就是首节点。
                node = cycleNode.next
                # 从cycleNode.next -> cycleNode 遍历整个环
                while node != cycleNode:
                    if node == head:
                        # 某个节点出现在环中
                        return head
                        # 立即返回,此时的节点及时首节点
                    node = node.next
                head = head.next

            return cycleNode
            # 如果一直都找不到,说明cycleNode就是首节点,首节点之前不会再有任何节点在环中。

        
    def wing(self, head: ListNode) -> ListNode:
        # 检测是否有环,无环返回 None, 有环则返回环中任何一个节点

        if head:
            slow = head
            fast = head.next
        else:
            return None
        
        while slow != None and fast != None and fast.next != None:
            if slow == fast:
                return fast
            slow = slow.next
            fast = fast.next.next

        return None
        

Java 方法二:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode cycleNode = wing(head);
        if(cycleNode == null){
            return null;
        }
        while (head != cycleNode){
            ListNode node = cycleNode.next;
            while(node != cycleNode){
                if(node == head){
                    return head;
                }
                node = node.next;
            }
            head = head.next;
        }
        return cycleNode;
    }
    public ListNode wing(ListNode head){
        if(head == null){
            return null;
        }else{
            ListNode slow = head;
            ListNode fast = head.next;
            while(slow != null && fast != null && fast.next != null){
                if(slow == fast){
                    return fast;
                }
                slow = slow.next;
                fast = fast.next.next;
            }
            return null;
        }
    }
}

Python 方法三: 终极解法,将双指针贯彻到底。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        cycleNode = self.wing(head)
        if cycleNode == None:
            return None
        else:
            outter = head
            inner  = cycleNode
            while outter != inner:
                outter = outter.next
                inner = inner.next
            return outter

    def wing(self, head: ListNode) -> ListNode:
        if head == None:
            return None
        else:
            slow = head
            fast = head
            while slow != None and fast != None and fast.next != None:
                slow = slow.next
                fast = fast.next.next
                if slow == fast:
                    return fast
            return None

易错点:

  • 使用双指针法的时候,主要 fast.next fast.next.next 需要先保证 fast != None and fast.next != None
  • 快慢双指针相遇的位置确定在环上,但是不确定在环上的哪个位置。

总结:

为了 0.2M内存,至于嘛 ?
在这里插入图片描述


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值