【链表】leetcode142.环形链表II(C/C++/Java/Js)

news2024/12/23 5:56:21

leetcode142.环形链表II

  • 1 题目
  • 2 思路
    • 2.1 判断链表是否有环--快慢指针法
    • 2.2 如果有环,如何找到这个环的入口
    • 2.3 补充
  • 3 代码
    • 3.1 C++版本
    • 3.2 C版本
    • 3.3 Java版本
    • 3.4 JavaScript版本
  • 4 总结


1 题目

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

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置 (索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

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

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

示例 3:
在这里插入图片描述
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

提示:

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


2 思路

这里大家可以去看:Carl老师的视频讲解
思路整理也是摘录Carl老师的代码随想录


这道题目,不仅考察对链表的操作,而且还需要一些数学运算
主要考察两知识点:

  • 判断链表是否环
  • 如果有环,如何找到这个环的入口

2.1 判断链表是否有环–快慢指针法

快慢指针法

分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。

那么来看一下,为什么fast指针和slow指针一定会相遇呢?

可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。

会发现最终都是这种情况, 如下图:
在这里插入图片描述
fast和slow各自再走一步, fast和slow就相遇了。

这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

动画


2.2 如果有环,如何找到这个环的入口

此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
在这里插入图片描述
那么相遇时:
slow指针走过的节点数为: x + y
fast指针走过的节点数:x + y + n (y + z)
nfast指针在环内走了n圈才遇到slow指针,
(y+z) 为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点,
所以 fast指针走过的节点数 = slow指针走过的节点数 * 2

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

两边消掉一个(x+y):
x + y = n (y + z)

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离

所以要求x ,将x单独放在左面:
x = n (y + z) - y ,

再从n(y+z)中提出一个 (y+z)来,
整理公式之后为如下公式:
x = (n - 1) (y + z) + z

注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。


-这个公式说明什么呢?

拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当 n为1的时候,公式就化解为 x = z

这就意味着,

从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点
动画


那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点


2.3 补充

在推理过程中,大家可能有一个疑问就是:
为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?
在这里插入图片描述
首先slow进环的时候,fast一定是先进环来了。

如果slow进环入口,fast也在环入口,那么把这个环展开成直线,就是如下图的样子:
在这里插入图片描述
可以看出如果slow 和 fast同时在环入口开始走,一定会在环入口3相遇,slow走了一圈,fast走了两圈。

重点来了,slow进环的时候,fast一定是在环的任意一个位置,如图:
在这里插入图片描述
那么fast指针走到环入口3的时候,已经走了k + n 个节点,slow相应的应该走了(k + n) / 2 个节点。

因为k是小于n的(图中可以看出),所以(k + n) / 2 一定小于n。

也就是说 slow一定没有走到环入口3,而fast已经到环入口3了

这说明什么呢?

在slow开始走的那一环已经和fast相遇了。

那有同学又说了,为什么fast不能跳过去呢? 在刚刚已经说过一次了,fast相对于slow是一次移动一个节点,所以不可能跳过去。


3 代码


3.1 C++版本

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
            if (slow == fast) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2; // 返回环的入口
            }
        }
        return NULL;
    }
};

3.2 C版本

ListNode *detectCycle(ListNode *head) {
    ListNode *fast = head, *slow = head;
    while (fast && fast->next) {
        // 这里判断两个指针是否相等,所以移位操作放在前面
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) { // 相交,开始找环形入口:分别从头部和从交点出发,找到相遇的点就是环形入口
            ListNode *f = fast, *h = head;
            while (f != h) f = f->next, h = h->next;
            return h;
        }
    }
    return NULL;
}

3.3 Java版本

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {// 有环
                ListNode index1 = fast;
                ListNode index2 = head;
                // 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}

3.4 JavaScript版本

// 两种循环实现方式
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
// 先判断是否是环形链表
var detectCycle = function(head) {
    if(!head || !head.next) return null;
    let slow =head.next, 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;
};

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

4 总结

主要考察两知识点:

  • 判断链表是否环
  • 如果有环,如何找到这个环的入口

自己动手推算一遍公式便很好理解啦~
如果还有不懂可以去看Carl老师:

Carl老师的视频讲解
思路整理也是摘录Carl老师的代码随想录

By Suki —2023/1/15

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/165378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软测复习05:基于质量特征的测试

作者&#xff1a;非妃是公主 专栏&#xff1a;《软件测试》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录性能测试压力测试容量测试健壮性测试安全性测试可靠性测试恢复性测试协议一致性测试兼容性测试安装…

【数据结构】保姆级单链表教程(概念、分类与实现)

目录 &#x1f34a;前言&#x1f34a;&#xff1a; &#x1f348;一、链表概述&#x1f348;&#xff1a; 1.链表的概念及结构&#xff1a; 2.链表存在的意义&#xff1a; &#x1f353;二、链表的分类&#x1f353;&#xff1a; &#x1f95d;三、单链表的实现&#x1f…

​盘点几款国内外安全稳定的域名解析平台​

众所周知&#xff0c;有了域名后想建站使用&#xff0c;必须要先解析域名。域名使用注册商一般会提供域名解析服务&#xff0c;这虽然为用户提供了方便&#xff0c;但功能大多有限&#xff0c;使用第三方域名解析平台就成了非常必要的选择。今天&#xff0c;小编就为大家盘点几…

计算机视觉OpenCv学习系列:第四部分、键盘+鼠标响应操作

第四部分、键盘鼠标响应操作第一节、键盘响应操作1.键盘响应事件2.键盘响应3.代码练习与测试第二节、鼠标操作与响应1.鼠标事件与回调2.鼠标操作3.代码练习与测试学习参考第一节、键盘响应操作 键盘响应中有一个函数叫做waitKey&#xff0c;所有的获取键盘键值都是通过waitKey…

【经典笔试题】动态内存管理

test1&#xff1a;void GetMemory(char* p) {p (char*)malloc(100); } void Test(void) {char* str NULL;GetMemory(str);strcpy(str, "hello world");printf(str); }int main() {Test();return 0; }请问执行上面代码&#xff0c;会出现什么结果&#xff1f;解析&a…

7. R语言【独立性检验】:卡方独立性检验、Fisher精确检验 、Cochran-Mantel-Haenszel检验

文章目录1. 卡方检验2. 费希尔精确检验&#xff08;Fisher Exact Test&#xff09;3. Cochran-Mantel-Haenszel检验独立性检验&#xff1a;用来判断变量之间相关性的方法&#xff0c;如果两个变量彼此独立&#xff0c;那么两者统计上就是不相关的 1. 卡方检验 可以使用chisq.…

Java面向对象之多态、内部类、常用API

目录面向对象之三大特性之三&#xff1a;多态多态的概述、多态的形式多态的好处多态下引用数据类型的类型转换多态的综合案例内部类内部类概述内部类之一&#xff1a;静态内部类内部类之二&#xff1a;成员内部类内部类之三&#xff1a;局部内部类内部类之四&#xff1a;匿名内…

JavaSE与网络面试题

大佬的&#xff1a; https://github.com/Snailclimb/JavaGuide https://osjobs.net/topk/all/ 自增自减 要点&#xff1a; 赋值 &#xff0c;最后计算 右边的从左到右加载值&#xff0c;一次压入操作数栈 实际先算哪个看运算符的优先级 自增、自减操作都是直接修改变量…

SpringCloud面试题

为什么需要学习Spring Cloud 不论是商业应用还是用户应用&#xff0c;在业务初期都很简单&#xff0c;我们通常会把它实现为单体结构的应用。但是&#xff0c;随着业务逐渐发展&#xff0c;产品思想会变得越来越复杂&#xff0c;单体结构的应用也会越来越复杂。这就会给应用带…

带你走入虚函数和多态的世界(c++)

1、什么是虚函数 C类中用virtual修饰的函数叫做虚函数&#xff0c;构造函数没有虚构造函数&#xff0c;存在虚析构函数&#xff0c;C所有虚函数都是一个指针去存储的&#xff0c;所以具有虚函数的类&#xff0c;内存会增加一个指针大小的内存 #include<iostream> #includ…

第一章:计算机网络概述

一、计算机网络基本概念 1、什么是计算机网路&#xff1f; 计算机网络是通信技术与计算机技术紧密结合的产物。计算机网络就是一种特殊的通信网络&#xff0c;其特别之处就是&#xff0c;其信源和信宿通常就是我们所说的计算机&#xff0c;发出的信息通常就是数字化的一些信息…

数据分析-深度学习 Pytorch Day5

李宏毅《机器学习》第6讲——梯度下降Review: 梯度下降法在回归问题的第三步中&#xff0c;需要解决下面的最优化问题&#xff1a;我们要找一组参数θ &#xff0c;让损失函数越小越好&#xff0c;这个问题可以用梯度下降法解决。假设θ有里面有两个参数θ1,θ2&#xff0c;随机…

FPGA 20个例程篇:19.OV7725摄像头实时采集送HDMI显示(一)

第七章 实战项目提升&#xff0c;完善简历 19.OV7725摄像头实时采集送HDMI显示&#xff08;一&#xff09; 在例程“OV7725摄像头实时采集送HDMI显示”中&#xff0c;我们将走近FPGA图像处理的世界&#xff0c;图像处理、数字信号、高速接口也一直被业界公认为FPGA应用的三大主…

k8s ingress概念和实践

什么是Ingress Ingress 是对集群中服务的外部访问进行管理的 API 对象&#xff0c;典型的访问方式是 HTTP/HTTPS 该特性从1.19版本开始作为stable状态进行发布 Ingress 公开从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源上定义的规则控制。 如下…

Python算法:三种简单排序的方法

目录 前言 1、插入排序 实例 2、选择排序 实例 3、冒泡排序 实例 前言 声明&#xff1a;本文所有动图来源为菜鸟教程 &#x1f340;作者简介&#xff1a;被吉师散养、喜欢前端、学过后端、练过CTF、玩过DOS、不喜欢java的不知名学生。 &#x1f341;个人主页&#xff1a;红…

监督学习、半监督学习、无监督学习、自监督学习、强化学习和对比学习

目录 一、监督学习 二、半监督学习 三、无监督学习 3.1.聚类算法 3.2.降维算法 3.3.异常检测 3.4.自动编码器 3.5.生成模型 3.6.关联规则学习 3.7.自组织映射(SOM) 四、自监督学习 4.1. 基于上下文&#xff08;Context based&#xff09; 4.2. 基于时序&#xff08…

85.【Vue-细刷-01】

Vue(一)、利用Vscode开发Vue1.在桌面创建文件夹code2.使用Vscode进行打开这个文件夹3.在Vscode的右键创建文件夹4.右键创建.html文件(二)、第一个Vue程序1.下载并引入Vue.js2.引入vue.js3.创建视图层4.创建Model层5.获取Vue实列中的数据6.效果展示⭐为什么要使用new 关键字来创…

文件操作(二):学习笔记10

目录 一.概念梳理 1.文件的分类 2.程序运行时&#xff0c;内存和外存数据交流的过程 二.文件缓冲区 三.常用的文件读写函数笔记 1.常用格式化文件读写函数 (1)格式化文件写入函数 用fprintf进行文件写入操作&#xff1a; (2)格式化文件读取函数 用fscanf进行文件读取 2…

Java枚举类与注解

目录 一、枚举类的使用 枚举类的实现 枚举类的属性 自定义枚举类 步骤 举例 使用enum定义枚举类 使用说明 举例 Enum类的主要方法 实现接口的枚举类 二、注解的使用 概述 常见的Annotation示例 自定义Annotation&#xff08;参照SupressWarnings 定义&#xff09…

一不小心,登上支付宝开发者社区热文榜单Top3

大家好&#xff0c;我是小悟 那天中午要午休的时候&#xff0c;看到微信通讯录新朋友有个红色1&#xff0c;像俺这种有强迫症的&#xff0c;那不得去把它点掉。打开一看&#xff0c;加好友的备注是“我是熊二&#xff0c;支付宝开发者社区运营”。 收到支付宝社区的运营添加微…