算法 1.5.1 环形链表 II【leetcode 142】

题目描述

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

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)
如果 pos 是 -1,则在该链表中没有环
注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中

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

进阶:你是否可以使用 O(1) 空间解决此题?

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

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

提示:

  • 链表中节点的数目范围在范围 [0, 10^4] 内
  • -10^5<= Node.val <= 10^5
  • pos 的值为 -1 或者链表中的一个有效索引

数据结构

  • 链表(题目自带)、指针变量

算法思维

  • 遍历、快慢指针、逆推、数学思维:环的同步

解题要点

  • 环长度的计算
  • 数学思维的运用:环的同步
    相同速度下,领先一个环长的指针将和从 head 出发的指针在环的首节点相遇,并从此进入同步

解题思路

一. Comprehend 理解题意
  • 需要判断链表中是否有环
  • 需要找到环的第一个节点
  • 不能破坏或改变环的结构和数据

可以使用“追击问题”的思路来判断环的存在,但如何找到环的第一个节点呢?

首先想到的肯定是给链表加一个 int 类型的属性,相当于索引或者编号,但题目明确要求了“不能破坏或改变环的结构和数据”,所以这种方法肯定是不行的

题意是希望我们使用遍历的方式,不改变链表,光靠“看”和“算”,找到环的首节点,而一旦涉及到“算”,我们就必须使用数学方法来解决问题

二. Choose 选择数据结构与算法
解法一:一快两慢指针法
  • 数据结构:指针变量
  • 算法思维:遍历、快慢指针、逆推、数学思维:环的同步
(一) 环首节点的定位

要定位到链表中的某个节点,需要特定的判断条件
要创造判断条件,我想到如下的两个方式:

  1. 索引
    虽然题目中不允许给链表添加属性,但是我们可以使用一个 int 类型的变量 n,实时记录当前遍历的位置。
    如果我们通过某个数学公式,计算出了环的首节点在链表中位置,就可以通过索引来控制遍历的步数,从而使用条件 i == n 进行定位:
ListNode li = head;
for (int i=0; i<n i++) {
    li = li.next;
}
return li;

很可惜,经过各种尝试,仍然没能找到首节点位置与相遇位置之间的关系,即无法通过快慢指针相遇的位置计算出首节点索引,只能再想其他办法

  1. 相遇
    如果有两个指针 a 和 b 恰好在环的首节点相遇,这时就可以使用条件 a == b 进行定位:
while (a != b) {...}
return a;

那么,该如何控制两个指针,让它们恰好在环的首节点相遇呢?

  • 环形链表 I 题中可知,快慢指针并不一定会相遇在环的首节点,因此使用快慢指针肯定是不行的,故此考虑使用两个速度均为 1个步长 的慢指针,方便控制

这里可以使用 反推 / 逆推 的方法,去“凑”相遇条件。

想象一个环形的跑道(如上图),两个小人若要在起点相遇,则需要满足两个条件:

  1. 两个小人速度相同
  2. 黄色小人刚好领先蓝色小人一圈的距离

于是得出结论:相同速度下,领先一个环长的指针将和从 head 出发的指针在环的首节点相遇,并从此进入同步

(二) 环长的计算

明晰了相遇条件(步长相等 + 领先一个环长),接下来就是如何计算环的长度了,环长的计算比较容易:

  1. 定义快慢两个指针:
    slow:从 head 开始,逐个节点遍历链表(步长为 1)
    fast:从 head.next 开始,跨一个节点遍历链表(步长为 2)
  2. slow 和 fast 相遇时走过的 “步数n” 是相同的,因此 fast 走过的距离是 2n,slow 走过的距离是 n
  3. 由相遇条件,二者相遇时 fast 比 slow 多走了一圈,即有:
    2n + 1 = n + 环长 --> 环长 = 2n-n+1 = n+1

至此,所有的准备工作都已经完毕,可以开始实现思路了!!

三. Code 编码实现基本解法
实现步骤:
  1. 声明一个用来记录步数的变量
  2. 声明快慢两个指针
  3. 以不同的步长遍历链表
  4. 先判断是否有环
  5. 如果有环,计算环的长度 = n+1
  6. 声明一个新的慢指针,从 head 开始遍历
  7. slow 前进一步,此时 slow 领先了 slow2 一个 cycLen
  8. 以一倍步长遍历链表,直到 slow 和 slow2 相遇
  9. 相遇的位置即为环的第一个节点
代码如下:
public class Solution {
    public ListNode detectCycle(ListNode head) {

        //0.非空判断
        if (head == null) return null;

        //1.声明一个用来记录步数的变量
        int n = 0;

        //2.声明快慢两个指针
        ListNode slow = head;
        ListNode fast = head.next;

        //3.以不同的步长遍历链表
        while (fast != null && fast.next != null) {
            //4.先判断是否有环
            if (slow == fast) {
                //5.如果有环,计算环的长度 = 1 + fast 路程 - slow 路程 = 1 + 2n - n = n+1
                int cycLen = n+1; //这一步只是方便理解,可以省略
                //6.声明一个新的慢指针,从 head 开始遍历
                ListNode slow2 = head;
                //7.slow 前进一步,此时 slow 领先了 slow2 一个 cycLen
                slow = slow.next;
                //8.以一倍步长遍历链表,直到 slow 和 slow2 相遇
                while (slow != slow2) {
                    slow = slow.next;
                    slow2 = slow2.next;
                }
                //9.相遇的位置即为环的第一个节点
                return slow;
            }
            slow = slow.next;
            fast = fast.next.next;
            n++;
        }

        return null;
    }
}

执行耗时:0 ms,击败了 100.00% 的Java用户
内存消耗:38.2 MB,击败了 97.56% 的Java用户
时间复杂度:O(n) -- 链表的遍历 O(n)
空间复杂度:O(1) -- 3个指针变量的内存空间 O(n)

四. Consider 思考更优解

=== 暂无 ===

五. Code 编码实现最优解

=== 暂无 ===

六. Change 变形与延伸

=== 待续 ===

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容