• 力扣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
    解释:链表中没有环。
    

    进阶:

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

    原题url:https://leetcode-cn.com/problems/linked-list-cycle-ii/

    解题

    在这里贴一下题目所提供的节点结构,这样下面的代码就不重复贴了:

    Definition for singly-linked list.
    class ListNode {
        int val;
        ListNode next;
        ListNode(int x) {
            val = x;
            next = null;
        }
    }
    

    利用集合

    拿到题目的时候,一开始想到的就是利用集合,存储已经遍历过的节点,如果访问到 null,说明不是环;如果添加失败,说明已经添加过,那么一定是环,并且该节点就是环的入口;

    顺便说一句,我认为集合所占空间应该不是很大,因为它只是存储对象的应用地址,当然了,集合本身也是一个新的对象,也会占用额外的空间。

    让我们看看代码:

    public class Solution {
        public ListNode detectCycle(ListNode head) {
            if (head == null) {
                return null;
            }
    
            ListNode current = head;
            Set<ListNode> set = new HashSet<>();
            while (current != null) {
                // 添加成功,则继续访问下一个节点
                if (set.add(current)) {
                    current = current.next;
                    continue;
                }
    
                // 添加不成功,说明重复
                return current;
            }
    
            return null;
        }
    }
    

    提交OK,执行用时:5 ms,内存消耗:37.7 MB,但是提交用时只战胜了30.99%的 java 提交记录,看来有必要优化一下。

    找规律

    以前我们判断链表是否有环,都是通过快慢指针最终是否相等。现在的话,因为环可能并不是首尾相连,所以只找一次可能不够了,需要继续寻找规律。

    我们假设一开始 slow 指针走过的路程为 x,那么 fast 指针走过的路程就为 2x,即:

    s = x;
    f = 2x;
    

    如果 fast 指针最终为 null,那么说明不是环。

    如果 fast、slow 指针最终指向的节点相等,说明有环,并且, fast 指针比 flow 指针多走了 n 圈环的长度,那么我们假设环的长度为 b,那么可以得出:

    f = x + nb;
    

    可以得出:s = nb;

    以上就是最重要的结论了,slow 指针其实也已经走了 n 圈环的长度了。那么,我们再假设从 head 节点到环入口节点的长度为 a,那么从快慢指针相遇节点再走 a 步,最终会走到哪儿呢?

    最终也会走到环的入口节点,因为(nb + a)可以理解为(a + nb),相当于从 head 节点出发,达到环的入口节点处,又绕环走了 n 圈,所以也会走到环的入口。所以此时我们也找到环的入口节点了。

    接下来让我们看看代码:

    public class Solution {
        public ListNode detectCycle(ListNode head) {
            // 先利用快慢指针,如果最终能相遇,说明有环
            ListNode slow = head;
            ListNode fast = head;
            while (true) {
                // 快指针为null,说明没有环
                if (fast == null || fast.next == null) {
                    return null;
                }
    
                // 慢指针移动一步
                slow = slow.next;
                // 快指针移动两步
                fast = fast.next.next;
                // 快慢指针相等,说明相遇
                if (fast == slow) {
                    break;
                }
            }
    
            // 再用两个指针,一个从头结点出发,一个从相遇点出发,两个指针每次移动1步,两个指针相遇的地方为环的入口
            slow = head;
            while (slow != fast) {
                slow = slow.next;
                fast = fast.next;
            }
    
            return slow;
        }
    }
    

    提交OK,执行用时:1 ms,内存消耗:37.8 MB,但是提交用时只战胜了55.14%的 java 提交记录,难道还有更加高效的方法?

    我找了一个执行用时 0 ms 的代码,发现就是和我这个类似的,我将它的代码再次提交后,发现和我这个提交结果一样。看来那些比我们快的算法,可能是因为提交时间比较早,测试案例并不像现在那么多,所以不必担心了。

    总结

    以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题目不仅要利用快慢指针,还要总结规律,最终也能解决,总的来说是一道很考验逻辑思维的题目。

    有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

    https://www.death00.top/

    公众号:健程之道

  • 相关阅读:
    Selenium系列(十五)
    Selenium系列(十四)
    Selenium系列(十三)
    Selenium系列(十二)
    Linux常用命令
    Linux
    Linux常用命令
    Linux常用命令
    Mysql常用sql语句(2)- 操作数据表
    Linux常用命令
  • 原文地址:https://www.cnblogs.com/death00/p/12147923.html
Copyright © 2020-2023  润新知