环形链表之快慢指针

2023-11-08

前言

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

一、案例

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;
            //跟新pre1
            pre1 = 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;
    }

    //环形链表II
    public 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 = kN
        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;
        }
    }
}

总结

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

参考文献

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

环形链表之快慢指针 的相关文章

  • 菜单未显示在应用程序中

    由于某种原因 我的操作菜单在我的 Android Studio 应用程序中消失了 我正在按照教程学习如何创建 Android 应用程序 但最终遇到了这个问题 我正在使用 atm 的教程 http www raywenderlich com
  • Java 中的 XPath 节点集

    我在 eclipse 中有这段代码 NodeSet nodes NodeSet xPath evaluate expression inputSource XPathConstants NODESET 它给我 NodeSet 上的编译时错误
  • 如何在 JFace 的 TableViewer 中创建复选框?

    我创建了一个包含两列的 tableViewer 我想将其中一列设为复选框 为此 我创建了一个 CheckBoxCellEditor 但我不知道为什么它不起作用 名为 tableName 的列显示其值正常 色谱柱规格如下 String COL
  • 在Windows上安装Java 11 OpenJDK(系统路径问题)

    Java 11 最近发布了 众所周知 这个版本没有安装文件 当然 要在没有安装程序的情况下安装 Java 我将系统设置 PATH 和 JAVA HOME 设置为解压缩 Java 11 的文件夹的地址 根据对类似问题的已接受回复建议 唯一的事
  • Android Studio 在编译时未检测到支持库

    由于 Android Studio 将成为 Android 开发的默认 IDE 因此我决定将现有项目迁移到 Android studio 中 项目结构似乎不同 我的项目中的文件夹层次结构如下 Complete Project gt idea
  • java.io.IOException: %1 不是有效的 Win32 应用程序

    我正在尝试对 XML 文档进行数字签名 为此我有两个选择 有一个由爱沙尼亚认证中心为程序员创建的库 还有一个由银行制作的运行 Java 代码的脚本 如果使用官方 认证中心 库 那么一切都会像魅力一样进行一些调整 但是当涉及到银行脚本时 它会
  • 使用 ANTLR 为 java 源代码生成抽象语法树

    如何使用 ANTLR 从 java src 代码生成 AST 有什么帮助吗 好的 步骤如下 前往ANTLR站点 http www antlr org 并下载最新版本 下载Java g和JavaTreeParser g文件来自here htt
  • Convert.FromBase64String 方法的 Java 等效项

    Java 中是否有相当于Convert FromBase64String http msdn microsoft com en us library system convert frombase64string aspx which 将指
  • java中删除字符串中的特殊字符?

    如何删除字符串中除 之外的特殊字符 现在我用 replaceAll w s 它删除了所有特殊字符 但我想保留 谁能告诉我我该怎么办 Use replaceAll w s 我所做的是将下划线和连字符添加到正则表达式中 我添加了一个 连字符之前
  • 如何在 Java 中禁用 System.out 以提高速度

    我正在用 Java 编写一个模拟重力的程序 其中有一堆日志语句 到 System out 我的程序运行速度非常慢 我认为日志记录可能是部分原因 有什么方法可以禁用 System out 以便我的程序在打印时不会变慢 或者我是否必须手动检查并
  • 一种使用 Java Robot API 和 Selenium WebDriver by Java 进行文件上传的解决方案

    我看到很多人在使用 Selenium WebDriver 的测试环境中上传文件时遇到问题 我使用 selenium WebDriver 和 java 也遇到了同样的问题 我终于找到了解决方案 所以我将其发布在这里希望对其他人有所帮助 当我需
  • Java 页面爬行和解析之 Crawler4j 与 Jsoup

    我想获取页面的内容并提取其中的特定部分 据我所知 此类任务至少有两种解决方案 爬虫4j https github com yasserg crawler4j and Jsoup http jsoup org 它们都能够检索页面的内容并提取其
  • 使用替换字符串中多个单词的最有效方法[重复]

    这个问题在这里已经有答案了 此刻我正在做 Example line replaceAll replaceAll cat dog replaceAll football rugby 我觉得那很丑 不确定有更好的方法吗 也许循环遍历哈希图 ED
  • Java中接口作为方法参数

    前几天去面试 被问到了这样的问题 问 反转链表 给出以下代码 public class ReverseList interface NodeList int getItem NodeList nextNode void reverse No
  • 检查 protobuf 消息 - 如何按名称获取字段值?

    我似乎无法找到一种方法来验证 protobuf 消息中字段的值 而无需显式调用其 getter 我看到周围的例子使用Descriptors FieldDescriptor实例到达消息映射内部 但它们要么基于迭代器 要么由字段号驱动 一旦我有
  • Springs 元素“beans”不能具有字符 [children],因为该类型的内容类型是仅元素

    我在 stackoverflow 中搜索了一些页面来解决这个问题 确实遵循了一些正确的答案 但不起作用 我是春天的新人 对不起 这是我的调度程序 servlet
  • 如何测试 spring-security-oauth2 资源服务器安全性?

    随着 Spring Security 4 的发布改进了对测试的支持 http docs spring io spring security site docs 4 0 x reference htmlsingle test我想更新我当前的
  • 中断连接套接字

    我有一个 GUI 其中包含要连接的服务器列表 如果用户单击服务器 则会连接到该服务器 如果用户单击第二个服务器 它将断开第一个服务器的连接并连接到第二个服务器 每个新连接都在一个新线程中运行 以便程序可以执行其他任务 但是 如果用户在第一个
  • Jackson 将单个项目反序列化到列表中

    我正在尝试使用一项服务 该服务为我提供了一个带有数组字段的实体 id 23233 items name item 1 name item 2 但是 当数组包含单个项目时 将返回该项目本身 而不是包含一个元素的数组 id 43567 item
  • Swagger/Openapi-Annotations:如何使用 $ref 生成 allOf?

    我正在生成 Rest 端点 包括添加OpenAPI Swagger对生成的代码进行注释 虽然它对于基本类型运行得很好 但我在自定义类方面遇到了一些问题 现在我有很多自定义类的重复架构条目 使用 Schema 实现 MyClass class

随机推荐

  • 微服务如何治理

    微服务远程调用可能有如下问题 注册中心宕机 服务提供者B有节点宕机 服务消费者A和注册中心之间的网络不通 服务提供者B和注册中心之间的网络不通 服务消费者A和服务提供者B之间的网络不通 服务提供者B有些节点性能变慢 服务提供者B短时间内出现
  • 每日学习07:Comparable接口的CompareTo的用法

    接口 Comparable 此接口强行对实现它的每个类的对象进行整体排序 这种排序被称为类的自然排序 类的 compareTo 方法被称为它的自然比较方法 字符串 数组列表 数组 所有可以 排序 的类都实现了java lang Compar
  • ThinkPHP5.1开发企业微信支付到零钱

    Wxpay php
  • npm启动vue应用开发服务器过程分析

    关于 npm run serve 命令启动vue应用开发环境的过程分析 1 npm run 命令执行时 npm run 命令执行时 会把 node modules bin目录添加到执行环境的PATH变量中 全局的没有安装的包 在node m
  • 本地IDEA中使用SonarQube扫描代码

    文章目录 背景 步骤 安装插件 配置 使用 背景 为了提高效率 在走代码CICD流程里的Sonarqube之前 先在本地提前进行一次扫描和修复 步骤 安装插件 2种方式 在IDE的插件管理中心安装名为 SonarQube Community
  • 爬虫高级应用(15. 基于Charles抓包软件抓取手机APP数据)

    目录 写在前面 配置安装Charles 安装Charles 下载相关证书 电脑证书 手机证书 设置代理 实操案例 抓取手机APP爱吾游戏宝盒数据 写在前面 移动App多使用异步的方式从服务端获取数据 抓取数据之前 要先分析移动App用于获取
  • 线性代数 --- 线性代数基本定理下(四个基本子空间他们两两正交,且互为正交补)

    正交子空间 前面我们已经知道了 两个向量的内积为0是勾股定理的另一种表现形式 现在我们来研究一下两个子空间之间的正交 虽然 我很不喜欢一上来就先给个定义 但我这里还是要给 sorry 现有两个子空间V和W 如果V中的任何一个向量v和W中的任
  • deepsort算法原理以及代码解析

    概述 前边我们讲了sort算法的原理 并且指出了它的不足 IDsw过大 为了解决该问题 17年时候sort算法的团队又提出了DeepSort算法 Deepsort在原来Sort算法的基础上 改进了以下内容 使用级联匹配算法 针对每一个检测器
  • .NET通用开发框架

    在开源中国社区 简单整理了下比较好的 NET通用开发框架 一个好的通用框架大概包括 开源 扩展性好 灵活性好 复用性好 维护性好 易测试 易发布 易部署 快速业务搭建 或业务集成 通用性强 参考资料多 持续技术支持 社区疑难问题建设 NET
  • 顺序表的基本操作(初始化、插入、删除、查询、扩容、打印、清空等)

    顺序表的基本操作 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构 一般情况下采用数组存储 在数组上完成数据的增删查改等基本操作 初始化 初始化结构体 开辟空间 void SeqListInit SeqList ps size
  • TypeScript 中的 any、unknown、never 和 void

    any any 表示 任意类型 它是任意类型的父类 任意类型的值都可以赋予给 any 类型 编译不会报错 let anything any 前端西瓜哥 let flag boolean true anything flag anything
  • 数据库实体关系模型 --- ER Model

    数据库实体关系图 The Entity Relationship Model ER Model ER模型的作用 ER模型的基本组成 E R 图 ER图的基本组成 不同的键 Key 超码 superkey 候选码 candidate key
  • 微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

    可能有些人会觉得这篇似曾相识 没错 这篇是由原文章进行二次开发的 前阵子有些事情 但最近看到评论区说原文章最后实现的是单模块的验证 由于过去太久也懒得验证 所以重新写了一个完整的可以跑得动的一个 OK 回到正题 以下是真正对应的微服务多模块
  • python习题及答案/4.16

    文章目录 1 从键盘输入两个数 求它们的和并输出 2 从键盘输入三个数到a b c中 按公式值输出 3 输出 Python语言简单易学 4 使用函数求特殊a串数列和 5 使用函数求素数和 6 使用函数统计指定数字的个数 1 从键盘输入两个数
  • 以太坊学习笔记(三)——搭建以太坊私链

    以太坊私链的搭建可以直接通过下载程序进行安装 也可以通过编译源码安装 本文介绍通过编译源码进行安装 编译源码 1 准备环境 我们下载的是go语言的源码 首先需要正确的安装go语言环境 如何正确安装go语言环境 大家可以去网上找教程 2 下载
  • AndroidO audio系统之AudioPolicyService分析(三)

    1 AudioPolicyService基础 AudioPolicy在Android系统中主要负责Audio 策略 相关的问题 它和AudioFlinger一起组成了Android Audio系统的两个服务 一个负责管理audio的 路由
  • QStringList 常用方法

    QStringList类 常用方法 定义一个字符串链表 QStringList weekList 往链表中添加元素 weekList lt lt 星期一 lt lt 星期二 lt lt 星期三 lt lt 星期四 weekList lt l
  • 麻雀算法SSA优化LSTM超参数

    前言 LSTM 航空乘客预测单步预测的两种情况 简单运用LSTM 模型进行预测分析 加入注意力机制的LSTM 对航空乘客预测采用了目前市面上比较流行的注意力机制 将两者进行结合预测 多层 LSTM 对航空乘客预测 简单运用多层的LSTM 模
  • Shapley Values

    今天来学习一下Shapley Values 先上概念 以及研究背景 borrowed from Wikipedia The Shapley value is a solution concept in cooperative game th
  • 环形链表之快慢指针

    环形链表 前言 一 案例 1 环形链表 2 环形链表II 二 题解 1 环形链表 2 环形链表II 3 源码 4 寻找入环点的数学解法 总结 参考文献 前言 对于环形链表 通过快慢指针 如果存在环 这这两个指针一定会相遇 这是一种经典的判断