力扣142——环形链表 II

旧时模样 提交于 2020-01-24 01:47:09

原题

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 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/

公众号:健程之道

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!