开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
前言
本系列文章主要会总结一些常见的算法题目以及算法的易错点,难点,以及一些万用的公式,并且结合实际的 Leetcode 题目来进行加深理解以及实际应用,算法这种东西,属于是一到用时方恨少的类型,在平时总结一些常见的简单算法,经常磨练自己的算法思维,对于日常的开发还是能有不少的帮助的。
- 今天来介绍一下环形链表
什么是环形链表
环形链表就是将单链表的尾部指向头部,从而形成一个单方向的环形结构,环形链表中每个元素都可以是head,也都可以是尾部
下面是一个正常的链表,里面有四个元素,最后指向null
正常的链表会区分头节点和尾节点,然后再来看一下环形链表
在链表中不再区分头节点和尾节点,但是需要为环形链表定义一个入口节点,用于判断遍历一类的结束条件。
简单的介绍了一下环形链表的结构,接下去来看一下题目。
Leetcode 142. 环形链表 II
题目的链表会是一个链表与环形链表相连接的一个结构,并且我们需要判断出链表和环形链表相交的点。
那么要做的事情就可以分为两个,首先是去判断链表中是否存在环形链表,然后再去计算出环形链表的入口位置。
是否存在环形链表
关于如何判断是否存在环形链表,我们可以定义快慢指针,一起从头节点出发,在某一次移动中,两个指针能够相遇,则代表存在环形链表,如果快指针指向了null,那就说明链表存在结尾,不是环形链表。
下面用一张动态图片来展示一下这个相遇的过程。
可以看到,在2这个位置,fast和slow相遇了,这就说明这个链表存在环,重点在于fast指针要在slow指针前面出发。
这就有点像1000米跑步的时候,你跑的太慢的话,就会被前面的人套圈,但是要是一千米跑的是一条直线,那无论如何他也没法套你的圈。
判断环的入口
看下面的一个例子
这个例子的移动过程就不做展示,直接放上最后的相遇节点,我们假设从头节点到入口节点的距离为 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;
};