环形链表之快慢指针

article/2025/11/9 23:02:10

环形链表

  • 前言
  • 一、案例
    • 1、环形链表
    • 2、环形链表II
  • 二、题解
    • 1、环形链表
    • 2、环形链表II
    • 3、源码
    • 4、寻找入环点的数学解法
  • 总结
  • 参考文献

前言

对于环形链表,通过快慢指针,如果存在环,这这两个指针一定会相遇,这是一种经典的判断环或是应用于环问题的思想。

一、案例

1、环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

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

如果链表中存在环 ,则返回 true 。 否则,返回 false 。
在这里插入图片描述

注:以O(1)内存进阶。

2、环形链表II

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

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

注:不允许修改 链表。

注:以O(1)内存进阶

二、题解

1、环形链表

1)可以直接遍历,把遍历的结点用Set记录下来,然后记录前查看set中是否有该节点。
2)对于O(1)内存进阶,
A)可以通过修改遍历过的节点的value为一个特殊值,然后一直遍历,如果中途碰到有value为特殊值,说明遍历过,即有环。
B)可每次遍历过之后,就修改前驱节点的next指向为相反的指向,不管是否有环,都不会在遍历中出现死循环,而是从左出即有环,从右出则无环。
C)快慢指针,可以通过快慢指针,就像在操场跑圈一样,速度快的能和速度慢能相遇。

2、环形链表II

1)从第一个的基础上,1)和2)的A)两种方式上都容易修改得来;而B)不适用;
2)对于C),可以得到最后相遇的地方,这个节点一定在环内,设为end节点。外层从头节点遍历到end节点,内层循环从end节点开始遍历,直到下一个end节点,看中途是否会碰到外层循环的中途节点。

3、源码

package com.xhu.offer.tencent;import java.util.HashSet;
import java.util.Set;//环形链表
public class HasCycle {public boolean hasCycle(ListNode head) {//一直next直到next == null 或者next到已经访问过的节点,分别返回true 和 false;Set<ListNode> mark = new HashSet<>();while (head != null) {if (mark.contains(head)) return true;mark.add(head);head = head.next;}return false;}//需要消耗O(1)内存来进阶public boolean hasCycle2(ListNode head) {//每向前走一步就改变链接方向,如果循环结束的最后一个节点是初始节点则有环。if (head == null || head.next == null) return false;ListNode point = head, pre1 = null, pre2;while (point.next != null) {//记录前向节点pre2 = point;//往后遍历point = point.next;//修改前向节点的next指向pre2.next = pre1;//跟新pre1pre1 = pre2;}return point == head;}//修改节点值public boolean hasCycle3(ListNode head) {while (head != null) {if (head.val == Integer.MAX_VALUE) return true;head.val = Integer.MAX_VALUE;head = head.next;}return false;}//快慢指针O(1)public boolean hasCycle4(ListNode head) {//快慢指针,如果有环,则一定会相遇。if (head == null || head.next == null) return false;ListNode point1 = head, point2 = head.next;while (point2 != null) {if (point1 == point2) return true;point1 = point1.next;point2 = point2.next;if (point2 == null) return false;point2 = point2.next;}return false;}//环形链表IIpublic ListNode detectCycle(ListNode head) {//以环形链表I的基础,返回节点即可//一直next直到next == null 或者next到已经访问过的节点Set<ListNode> mark = new HashSet<>();while (head != null) {if (mark.contains(head)) return head;mark.add(head);head = head.next;}return null;}//以O(1)内存进阶public ListNode detectCycle2(ListNode head) {//以环形链表I的基础,返回节点即可//一直next直到next == null 或者next到已经访问过的节点Set<ListNode> mark = new HashSet<>();while (head != null) {if (mark.contains(head)) return head;mark.add(head);head = head.next;}return null;}//以O(1)内存进阶,在不修改链表的情况。public ListNode detectCycle3(ListNode head) {//快慢指针,如果有环,则一定会相遇。这个时候就能确定这个相遇的点一定在环内。//然后从头开始遍历到环内节点,寻找入环节点。if (head == null || head.next == null) return null;ListNode point1 = head, point2 = head.next,end = null;while (point2 != null) {if (point1 == point2) {end = point1;break;}point1 = point1.next;point2 = point2.next;if (point2 == null) return null;point2 = point2.next;}if(end == null) return null;while(head != end){point1 = end.next;while(point1 != end){if(point1 == head) return point1;point1 = point1.next;}head = head.next;}return end;}// Definition for singly-linked list.class ListNode {int val;ListNode next;ListNode(int x) {val = x;next = null;}}
}

4、寻找入环点的数学解法

/*target:找到入环点。快慢指针 找到相遇点,此时起始节点到达相遇点的距离是环长的N倍。设新的起始点在环点,则从此处出发,到达起始点的距离 等于 环点再走n圈一致 - k。而入环点未知,而起始点未知,根据两者关系,反推起始点。*/public ListNode detectCycle(ListNode head) {// bug2:毕竟要先do,所以需要先判断链表是否为null.if (head == null) return null;// bug1:fast = head == null ? null : head.next;这样会导致其先走了一步,导致反推入环口时发生死循环。ListNode slow = head, fast = head;// 寻找相遇点。do {fast = fast.next;slow = slow.next;fast = fast == null ? null : fast.next;} while (slow != fast && fast != null);if (fast == null) return null;// 反推入环点。slow = head;while (slow != fast) {slow = slow.next;fast = fast.next;}return slow;}// Definition for singly-linked list.class ListNode {int val;ListNode next;ListNode(int x) {val = x;next = null;}}
}
// 非do while()
class DetectCycle2 {/*target:找到入环点。快慢指针 找到相遇点,此时起始节点到达相遇点的距离是环长的N倍。设新的起始点在环点,则从此处出发,到达起始点的距离 等于 环点再走n圈一致 - k。而入环点未知,而起始点未知,根据两者关系,反推起始点。*/public ListNode detectCycle(ListNode head) {ListNode slow = head, fast = head == null ? null : head.next;// 寻找相遇点。while (slow != fast && fast != null) {fast = fast.next;slow = slow.next;fast = fast == null ? null : fast.next;}if (fast == null) return null;// 反推入环点。slow = head;fast = fast.next;// a + b + 1 = kNwhile (slow != fast) {slow = slow.next;fast = fast.next;}return slow;}// Definition for singly-linked list.class ListNode {int val;ListNode next;ListNode(int x) {val = x;next = null;}}
}

总结

1)还是注意观察问题的个性之处,思考其中的细节,而不是简单的算法应用,比如修改节点的值或是修改节点的next指针。算法思想和代码只是工具,重要的是特定问题中的抽象逻辑。
2)对于简单应用,就是熟练使用set。
3)掌握快慢指针这样的经典算法思想,碰到环就得触发快慢指针的记忆,这样才显得专业一点。

参考文献

[1]LeetCode 环形链表
[2]LeetCode 环形链表II


http://chatgpt.dhexx.cn/article/P2KpRM8k.shtml

相关文章

快慢指针判断链表中是否存在环以及查找环的起始位置

判断链表中是否有环? 使用快慢指针, 慢指针一次走一步, 快指针一次走两步, 当快慢指针相遇时,说明链表存在环 为什么快指针每次走两步而慢指针每次走一步呢? 因为slow指针和fast指针都会进入环内, 就像在环形跑道内不同位置的两个人;slow指针在后面, fast指针在前面, 但…

链表-快慢指针(C++)

一、链表 链表是由一组在内存中不必相连&#xff08;不必相连&#xff1a;可以连续也可以不连续&#xff09;的内存结构Node&#xff0c;按特定的顺序链接在一起的抽象数据类型。 我们常见的链表结构有单链表和双向链表。 单链表&#xff0c;保存了下一个结点的指针&#xf…

面试题 02.08. 环路检测-快慢指针+如何找到环的入口?(证明)Java

1.题目 2.思路 方法一——哈希表记录节点 思路很简单&#xff0c;记录一下每个节点出现的次数&#xff0c;如果某个节点出现了两次&#xff0c;代表此时有环&#xff0c;并且是环的入口&#xff0c;直接返回即可。 时间复杂度O(N) 空间复杂度O(N) public class Solution {…

链表中快慢指针的应用

目录 一、链表的中间结点 二、回文链表 三、链表中倒数第K个结点 四、删除链表的倒数第n个结点 五、环形链表 六、环形链表Ⅱ 一、链表的中间结点 给定一个头结点为 head 的非空单链表&#xff0c;返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间…

快慢指针思想

快慢指针思想 在做题当中经常会用到快慢指针&#xff0c;快慢指针就是定义两根指针&#xff0c;移动的速度一快一慢&#xff0c;从而创造出自己想要指针的差值。这个差值可以让我们找到链表上相应的节点。 参考链接&#xff1a;https://www.jianshu.com/p/21b4b8d7d31b 应用 …

指针的运用——快慢指针

快慢指针是指针的一种类型&#xff0c;在这里我们来了解下快慢指针 让我们来看一道题 一、题目 876. 链表的中间结点 首先我们对这道题进行分析&#xff0c;最容易让人想到的方法是直接使用n/2找到中点&#xff0c;如果我们不对链表进行遍历&#xff0c;我们该怎么做呢&…

浅谈快慢指针

快慢指针 快慢指针 快慢指针1.快慢指针的概念&#xff1a;2.快慢指针的应用&#xff1a;1. 判断单链表是否为循环链表2. 在有序链表中寻找中位数3.链表中倒数第k个节点 1.快慢指针的概念&#xff1a; 快慢指针就是存在两个指针&#xff0c;一个快指针&#xff0c;一个慢指针&a…

快慢指针应用总结

快慢指针 快慢指针中的快慢指的是移动的步长&#xff0c;即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动2&#xff0c;慢指针每次向前移动1次。 快慢指针的应用 &#xff08;1&#xff09;判断单链表是否存在环 如果链表存在环&#xff0c;就好像操场的跑道是…

十大常用经典排序算法总结!!!

爆肝整理&#xff01;堪称全网最详细的十大常用经典排序算法总结&#xff01;&#xff01;&#xff01; 写在开头&#xff0c;本文经过参考多方资料整理而成&#xff0c;全部参考目录会附在文章末尾。很多略有争议性的细节都是在不断查阅相关资料后总结的&#xff0c;具有一定…

经典五大算法思想-------入门浅析

算法&#xff1a;求解具体问题的步骤描述&#xff0c;代码上表现出来是解决特定问题的一组有限的指令序列。 1、分治&#xff1a; 算法思想&#xff1a;规模为n的原问题的解无法直接求出&#xff0c;进行问题规模缩减&#xff0c;划分子问题&#xff08;这里子问题相互独立而且…

算法设计经典算法

一、贪婪算法 1、概述 贪婪法又称贪心算法&#xff0c;是当追求的目标是一个问题的最优解时&#xff0c;设法把对整个问题的求解工作分成若干步骤来完成&#xff0c;是寻找最优解问题的常用方法。 贪婪法的特点是一般可以快速得到满意的解&#xff0c;因为它省去了为找最优解…

算法之经典图算法

图介绍表示图的数据结构图的两种搜索方式DFS可以处理问题BFS可以处理问题有向图最小生成树最短路径 图介绍 图&#xff1a;是一个顶点集合加上一个连接不同顶点对的边的集合组成。定义规定不允许出现重复边&#xff08;平行边&#xff09;、连接到顶点自身的边&#xff08;自环…

计算机10大经典算法

算法一&#xff1a;快速排序法 快速排序是由东尼霍尔所发展的一种排序算法。在平均状况下&#xff0c;排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较&#xff0c;但这种状况并不常见。事实上&#xff0c;快速排序通常明显比其…

算法设计——五大算法总结

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 算法设计总结 一、【分治法】二、【动态规划法】三、【贪心算法】四、【回溯法】五、【分支限界法】 一、【分治法】 在计算机科学中&#xff0c;分治法是一种很重要的算法。…

十大经典算法总结

正文 排序算法说明 &#xff08;1&#xff09;排序的定义&#xff1a;对一序列对象根据某个关键字进行排序&#xff1b; 输入&#xff1a;n个数&#xff1a;a1,a2,a3,...,an 输出&#xff1a;n个数的排列:a1,a2,a3,...,an&#xff0c;使得a1<a2<a3<...<an。 再…

九大经典算法

1. 冒泡排序&#xff08;Bubble Sort&#xff09; 两个数比较大小&#xff0c;通过两两交换&#xff0c;像水中的泡泡一样&#xff0c;较大的数下沉&#xff0c;较小的数冒起来。 算法描述&#xff1a; 1.比较相邻的元素。如果第一个比第二个大&#xff0c;就交换它们两个&a…

最常用的五大算法

一、贪心算法 贪心算法&#xff08;又称贪婪算法&#xff09;是指&#xff0c;在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整…

几种经典算法

1.分治法 分治法也叫做分而治之法。核心思想是将一个难以直接解决的大问题依照相同的概念分割成两个或者多个相同的小问题&#xff0c;以便各个击破。 如图所示&#xff1a; 2.递归法 递归法和分而治之法像一对孪生兄弟&#xff0c;都是将一个复杂的算法问题进行分解&#x…

五大常用经典算法—分治算法

原文作者&#xff1a;bigsai 原文地址&#xff1a;五大常用算法&#xff1a;一文搞懂分治算法 目录 前言 分治算法介绍 分治算法经典问题 二分搜索 快速排序 归并排序(逆序数) 最大子序列和 最近点对 结语 前言 分治算法&#xff08;divide and conquer&#xff09;是…

十大经典算法

十大经典排序算法&#xff08;动图演示&#xff09; 0、算法概述 0.1 算法分类 十种常见排序算法可以分为两大类&#xff1a; 非线性时间比较类排序&#xff1a;通过比较来决定元素间的相对次序&#xff0c;由于其时间复杂度不能突破O(nlogn)&#xff0c;因此称为非线性时间比…