前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >力扣142——环形链表 II

力扣142——环形链表 II

作者头像
健程之道
发布2020-02-12 15:11:51
2470
发布2020-02-12 15:11:51
举报
文章被收录于专栏:健程之道健程之道

原题

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

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

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

示例 1:

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

示例 2:

代码语言:javascript
复制
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

代码语言:javascript
复制
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。

进阶:

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

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

解题

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

代码语言:javascript
复制
Definition for singly-linked list.
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) {
        val = x;
        next = null;
    }
}

利用集合

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

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

让我们看看代码:

代码语言:javascript
复制
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,即:

代码语言:javascript
复制
s = x;
f = 2x;

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

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

代码语言:javascript
复制
f = x + nb;

可以得出:s = nb;

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

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

接下来让我们看看代码:

代码语言:javascript
复制
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/

公众号:健程之道

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 健程之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原题
  • 解题
    • 利用集合
      • 找规律
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档