算法合集 | 链表 | Leetcode 142.环形链表II

106 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

前言

本系列文章主要会总结一些常见的算法题目以及算法的易错点,难点,以及一些万用的公式,并且结合实际的 Leetcode 题目来进行加深理解以及实际应用,算法这种东西,属于是一到用时方恨少的类型,在平时总结一些常见的简单算法,经常磨练自己的算法思维,对于日常的开发还是能有不少的帮助的。

  • 今天来介绍一下环形链表

什么是环形链表

环形链表就是将单链表的尾部指向头部,从而形成一个单方向的环形结构,环形链表中每个元素都可以是head,也都可以是尾部

下面是一个正常的链表,里面有四个元素,最后指向null

图片.png

正常的链表会区分头节点和尾节点,然后再来看一下环形链表

image.png

在链表中不再区分头节点和尾节点,但是需要为环形链表定义一个入口节点,用于判断遍历一类的结束条件。

简单的介绍了一下环形链表的结构,接下去来看一下题目。

Leetcode 142. 环形链表 II

题目的链表会是一个链表与环形链表相连接的一个结构,并且我们需要判断出链表和环形链表相交的点。

那么要做的事情就可以分为两个,首先是去判断链表中是否存在环形链表,然后再去计算出环形链表的入口位置。

是否存在环形链表

关于如何判断是否存在环形链表,我们可以定义快慢指针,一起从头节点出发,在某一次移动中,两个指针能够相遇,则代表存在环形链表,如果快指针指向了null,那就说明链表存在结尾,不是环形链表。

下面用一张动态图片来展示一下这个相遇的过程。

动画1.gif

可以看到,在2这个位置,fast和slow相遇了,这就说明这个链表存在环,重点在于fast指针要在slow指针前面出发。

这就有点像1000米跑步的时候,你跑的太慢的话,就会被前面的人套圈,但是要是一千米跑的是一条直线,那无论如何他也没法套你的圈。

判断环的入口

看下面的一个例子

image.png

这个例子的移动过程就不做展示,直接放上最后的相遇节点,我们假设从头节点到入口节点的距离为 x,从入口节点到相遇节点的距离为 y,从相遇节点到入口节点的距离为 z,从移动的过程当中,我们能够轻松的得出 快指针走过的节点个数,是慢指针的两倍 ,并且从图中可以得到,慢指针走过的节点数为 x+y 快指针走过的节点数为 x + y + (z+y)其中(z+y)就是说明快指针套了慢指针一圈,当然,快指针在某些时候肯定不止套一圈,比方说这个链表的前面比较长,那么快指针会早很多进入循环,然后循环又比较短的话,快指针就会在里面循环非常多次,所以根据上面的分析,我们就可以得到一个式子:

2(x + y) = x + y + n(z+y)

将这个式子进行化简,并且因为我们是要去寻找入口节点,也就是关注 x 那么就把 x 单独领出来,得到:

x = n (y + z) - y

根据上面的分析能够知道,y + z 就是一圈的距离,那么其实 n 个的一圈是没有意义的,始终会绕回到圈里面对应的点,那么我们就可以得到:

x = -y

并且因为 y + z 是可以直接约掉的,也就是约等于0,那么就可以得到 y+z=0,上面的式子就可以变成:

x = z

然后根据图来看,就不难发现,从开始点出发的指针,和相遇节点出发的指针,他们最后一定会在入口处相交。

那么到这里两个问题就都得到了解决,根据顺序解决两个问题,也就可以得出这道题的答案了。

题解

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     val: number
 *     next: ListNode | null
 *     constructor(val?: number, next?: ListNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.next = (next===undefined ? null : next)
 *     }
 * }
 */

function detectCycle(head: ListNode | null): ListNode | null {
    if(!head || !head.next) return null;
    let slow = head.next;
    let fast = head.next.next;
    while(fast && fast.next && fast!== slow) {
        slow = slow.next;
        fast = fast.next.next; 
    }
    if(!fast || !fast.next ) return null;
    slow = head;
    while (fast !== slow) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
};

image.png