『算法』趣说环形链表 II

499 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 1 天,点击查看活动详情

碎碎念🤥

大家好,我是潘小安,一个永远在减肥路上的前端er🐷 !

最近每天给自己安排了一道算法练习,本着 “独乐了不如众乐乐” 的分享精神以及对费曼学习法的深刻理解和实践需要(才不是来掘金进货),我决定每天记录一下每道经典算法题的解题思路,用简单、通俗、有趣的语言尽力把每道题目讲清楚。让我们开始吧 

上菜 🥬

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

image.png

image.png

观其色闻其味 🐽

首先,不动手,细品一下题目中的每个关键信息,结合例子我们可以有以下几点关键信息:

  • 链表可能没有环,没有返回 null
  • 有环的话要返回进入环的节点 那么问题来了:
  • 我们如何判断一个链表是否有环?
  • 一个有环的链表我们如何知道入口的那个点是哪个?

使用 Map

我们可以遍历链表,并使用 map 保存每一个走过的节点,当我们走到循环部分的入口的时候,会命中 map,这个时候直接返回,若没有环,则会遍历链表末尾的 null

双指针

双指针是链表问题中的常客,双指针顾名思义就是使用两个变量表示两个指针,对目标进行遍历的方法。在这个问题中,我们使用一个快指针和一个慢指针对链表进行遍历,并判断链表是否有环,无环则快指针会遍历到 null,问题是若有环,我们如何得到入环的那个点。

想这个问题之前,我们先来做一道生活情景题:

你和你大学室友从宿舍跑步去操场,由于你经常参加体育锻炼,所以你跑步的速度是 2m/s,你室友是一个阿宅,他跑步的速度是 1m/s,你们同时出发从宿舍去操场跑圈,请问你会在操场的哪个位置遇到你的室友?

是不是有点抽象?没关系,我们画图来理解:

image.png

image.png 你和室友从宿舍楼下 O 点出发,前后经过操场入口 t 点后,跑着跑着你会在 m 点遇到你室友。已知你的速度是 2m/s,室友的速度是 1m/s,操场一圈是四百米,那这个 t 点,也就是操场入口,距离宿舍楼多远呢?要弄清楚这点,我们先要弄清楚为什么是在 m 点。

我们换个思如果你们不是从宿舍出发,是从 t 点两个人跑圈,按照你们两个人的速度计算,理论上你们应该还会在 t 点相遇,你正好超过你室友整整一圈。那为什么你们会在 m 点相遇?因为你们还一起跑了宿舍到操场路口的这段路。

  • 假如宿舍到操场入口距离是就是四百米,这个 m 点在哪里?是不是就是操场入口?你呼哧呼哧的跑到操场入口跑了四百米,你室友还在半路,等你跑完了一圈,你室友才姗姗来迟刚刚到操场门口。

  • 假如宿舍到操场入口距离是一百米,这个 m 点在哪里?是不是在操场入口的逆时针方向的一百米处?因为当你室友在操场入口的时候,你已经在距离入口一百米的地方了,相当于你抢跑了一百米,那相遇的位置也提前一百米。

  • 假如宿舍到操场入口距离是五百米呢?当你室友跑到操场路口的时候,距离你们出发已经是过了五百秒,这个时候你在哪里?你已经跑了一圈还跑过了一百米,然后情况又回到了第二种情况。

image.png 如果用 x 表示宿舍到操场入口的距离,用 y 表示从入口到相遇点的顺时针距离,用 z 表示从相遇点到入口的剩余距离,我们可以得出结论:x = n(y+z) - y

n 表示跑的圈数,翻译一下这个公式就是,从宿舍到操场入口的距离,等于你跑了的圈数减去顺时针入口到相遇点的距离,当 n 等于 1 的时候,x = z,也就是我们的情况二的情况。当 n 等于 2 的情况的时候,其实就是情况三,你室友刚到起点,你已经跑了一圈多了。

那问题来了:我们要怎么把这个 zx 求出来?

答案是:魔法,就在那天,那个操场,你在 m 点追上你的室友的时候,你室友突然变身黑魔法师,将你瞬移回宿舍,并且给你套了个虚弱,然后继续跑步。你被虚弱了,速度降低到了 1m/s,你心想,被暗算了,我要找回场子,于是你重新向着操场跑去,不出意外,毫无疑问的,你们就会重新在操场入口 t 相遇。

说了这么多,我们回到我们的问题上面,我们定一个快指针和慢指针,一个每次移动两个节点,一个移动一个节点。如果链表存在环,快慢节点会在环中的 m 点相遇,这个时候 m 到入环点 t 的距离,和起点到入环口 t 的距离是符合我们上面推理得到的公式的。在第一次相遇的时候,将快指针重置到起点,并和慢指针保持匀速前进,再次相遇时,就是入环节点了。

下筷子 🥢

map解法

var detectCycle = function (head) {
    let map = new WeakMap()
    while (!!head) {
        if (map.get(head)) {
            return head
        } else {
            map.set(head, true)
            head = head.next
        }
    }
    return null
};

双指针法

var detectCycle = function(head) {
    if(!head || !head.next) return null;
    //你室友
    let slow =head.next;
    //你
    let fast = head.next.next;
    while(fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
        //你在 m 点追上了你室友
        if(fast == slow) {
            //你被黑魔法打中,被瞬移并减速
            slow = head;
            while (fast !== slow) {
                slow = slow.next;
                fast = fast.next;
            }
            //再次相遇
            return slow;
        }
    }
    //操场中间出现虫洞,进入后再也无法相遇
    return null;
};

小声BB🤫

以上就是 环形链表 II 相关的全部内容了。现在是北京时间 2022 年 04 月 02 日 23:35 分,即将开始清明假期,我也开始了我的四月更文全勤计划。神秘组织训练营第三期也要开始了,深圳的疫情也控制住了。但是上海疫情貌似有点失控的状态,各种流言,负面新闻满天飞。

image.png (感谢抱枕分享的图)

上海的小伙伴挺住!上海加油!中国加油!

🎉 觉得文章对您有帮助的小伙伴,请不要吝啬您的点赞~🎉

🎉 对文章中的措辞表达、知识点、文章格式等方面有任何疑问或者建议,请留下您的评论~🎉