当前位置: 首页 > news >正文

Java 算法篇-链表的经典算法:判断回文链表、判断环链表与寻找环入口节点(“龟兔赛跑“算法实现)

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
 

 

 

文章目录

        1.0 链表的创建

        2.0 判断回文链表说明

        2.1 快慢指针方法

        2.2 使用递归方式实现反转链表方法

        2.3 实现判断回文链表 - 使用快慢指针与反转链表方法

        3.0 判断环链表说明

        3.1 实现判断环链表与寻找环入口节点 - "龟兔赛跑"算法实现

        3.2 解释为什么第一次相遇后,兔、龟每一次都走一步最终会相遇且该节点是环入口节点的原因

        4.0 实现判断回文链表、判断环链表且寻找环入口节点的完整代码


 

        1.0 链表的创建

        链表是一种常见的数据结构,用于存储一系列元素。链表由节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。链表可以分为单向链表和双向链表,其中单向链表的节点只有一个指针指向下一个节点,而双向链表的节点有两个指针,分别指向前一个节点和后一个节点。        

        为后续实现算法方便,这里需要实现一个带哨兵节点的单链表

代码如下:

import java.util.Iterator;public class List implements Iterable<Integer>{private final Node sentry;static class Node {public int value;public Node next;public Node() {}public Node(int value, Node next) {this.value = value;this.next = next;}@Overridepublic String toString() {return "Node{" +"value=" + value + "}";}}//外部类构造器,初始化哨兵节点public List() {sentry = new Node(-1,null);}//头插节点public void addFirst(int value) {this.sentry.next = new Node(value,this.sentry.next);}//尾插节点public void addLats(int value) {Node p = this.sentry;while (p.next != null) {p = p.next;}p.next = new Node(value,null);}//重写迭代器@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p = sentry.next;@Overridepublic boolean hasNext() {return p != null;}@Overridepublic Integer next() {int k = p.value;p = p.next;return k;}};}}

        简单对以上代码进行分析:将链表进行封装成一个外部类静态内部类则是节点类进行封装。外部类的成员变量为一个哨兵节点,内部类的成员变量为 int value 值Node next 指向下一个节点的引用变量。外部类实现了头插节点尾插节点重写了迭代器等。

需要了解可以点击该链接:Java 数据结构篇-实现单链表核心API-CSDN博客

 

        2.0 判断回文链表说明

        回文链表是指一个链表从头到尾和从尾到头读是一样的,也就是说,链表的节点值按照顺序排列和逆序排列是相同的。例如,链表 1 -> 2 -> 3 -> 2 -> 1 就是一个回文链表,因为从头到尾读和从尾到头读都是 1 -> 2 -> 3 -> 2 -> 1。

        2.1 快慢指针方法

        实现判断回文链表时需要用到快慢指针方法来寻找中间节点

        具体思路:实现快慢指针找中间节点,定义两个指针,对于 fast 指针来说,每一次循环都要走两步,直到 fast == null 或者 fast.next == null,遇到这两种情况都要结束循环了,注意不要缺少了 fast.next == null 的情况,不然有可能抛出 "空指针异常" ;对于 slow 指针来说,每一次循环都要走一步,直到退出循环后,若链表的节点的数量为奇数时,则指向的节点就是中间节点。

        若链表的节点的数量为偶数时,则指向的节点是中间两个节点的后一个节点。例如链表 1 -> 2 -> 3 -> 3 -> 2 -> 1 -> null,此时循环结束后,slow 指针指向的是靠后面值为 3 的节点。

代码如下:

    //查找链表中的中间的节点(快慢指针):假如为奇数,则需要找到中间的节点;// 假如是偶数,则需要找到中间的两个节点的后一个节点。public Node searchMidNode() {//判断是否为空链表if (this.sentry.next == null) {return null;}Node fast = this.sentry.next;Node slow = this.sentry.next;while (fast!= null && fast.next != null) {fast = fast.next.next;slow = slow.next;}return slow;}

        2.2 使用递归方式实现反转链表方法

        实现判断回文链表时需要实现反转链表。

        具体思路:先考虑递出的终止条件为:当 p.next == null 时,则返回 p 这个节点。再考虑在回归的过程中,需要将该 p 节点一直回归到回归过程结束为止。还需要将每一个节点都需要反转一下,p.next.next = p,注意这里需要将 p.next "暂时" 置为 nullp.next = null,否则会陷入死循环中。

代码如下:

    //用递归实现链表反转public Node reverseRecursion(Node p) {if (p.next == null) {return p;}Node last = reverseRecursion(p.next);p.next.next = p;p.next = null;return last;}

         用递归实现链表反转是其中一种的方法,还有四种方法可以实现链表反转,需要了解可以点击一下链接:Java 算法篇-深入了解单链表的反转(实现:用 5 种方式来具体实现)-CSDN博客 

        2.3 实现判断回文链表 - 使用快慢指针与反转链表方法

        具体思路为:先找到链表中的中间节点,例如链表 1 -> 2 -> 3 -> 2 -> 1 -> null ,需要先找节点值为:3 的节点,可以用快慢指针来实现找中间节点。然后将该节点后面的链表( 3 -> 2 -> 1 -> null )进行反转,可以用递归来实现反转的链表,得 1 -> 2 -> 3 -> null 。接着,用旧链表进行与反转后的链表遍历比较,若出现不相同值的节点,则判断该链表不是回文链表;若遍历完都没有返回 false ,则判断该链表为回文链表。

代码如下:

    //查找链表中的中间的节点(快慢指针):假如为奇数,则需要找到中间的节点;// 假如是偶数,则需要找到中间的两个节点的后一个节点。public Node searchMidNode() {//判断是否为空链表if (this.sentry.next == null) {return null;}Node fast = this.sentry.next;Node slow = this.sentry.next;while (fast!= null && fast.next != null) {fast = fast.next.next;slow = slow.next;}return slow;}//用递归实现链表反转public Node reverseRecursion(Node p) {if (p.next == null) {return p;}Node last = reverseRecursion(p.next);p.next.next = p;p.next = null;return last;}//判断是否为回文链表public boolean isPalindromeList() {Node p = this.sentry.next;//需要先找到中间节点Node midNode = this.searchMidNode();//然后将中间节点往后的链表进行反转,反转可以用递归的方法。Node newMidNode = reverseRecursion(midNode);//接下来就要对旧节点的前半段链表进行循环遍历来比较了每一个节点的值是否相同了//当且仅当,当迭代到反转后的链表的最后一个为 null 时,结束循环while (newMidNode != null) {if (p.value != newMidNode.value) {return false;}p = p.next;newMidNode = newMidNode.next;}return true;}

        需要注意的是,对与 p 链表来说,一旦实现了链表反转, p 自身的链表会改变。反转之后的链表 newMidNode == null 时,就该结束循环了。而不能以 p == null 作为结束循环条件,原因是当链表的节点为偶数时,那么反转后的链表会比 p 链表少一个节点,假如用 p == null 作为结束循环的条件,那么当链表的节点数为偶数时,肯定会报 "空指针异常",所以需要以 newMidNode == null 作为循环结束条件

        3.0 判断环链表说明

        环链表是指链表中至少有一个节点的 next 指针指向了链表中的一个已经存在的节点,使得链表中存在环形结构。换句话说,链表中的一个节点的 next 指针指向了之前的某个节点,导致链表中存在环。

        3.1 实现判断环链表与寻找环入口节点 - "龟兔赛跑"算法实现

        具体思路:先来判断是否为环链表,可以比作为龟与兔的实际情景,当龟每一次走一步时,兔每一次走两步。即在相同时间下,兔所走的路程时龟的两倍

        情况一:当兔第一次没有追上龟时,则不是环链表,直接返回 null 。

        情况二:当兔第一次追上了龟时,可以判断为该链表为环形链表。接着寻找环入口,步骤为:可以借助兔子来记录第一次相遇的节点,对于龟来说,移到头节点开始一步步走,同时,兔子这次也是一步步走,当他们第二次相遇时,当前节点就为环入口节点。

代码如下:

    //判断是否闭环,如果是返回,则返回换入口;如果不是,则返回 nullpublic Node isLoop() {Node fast = this.sentry.next;Node slow = this.sentry.next;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) {slow = this.sentry.next;//特例:当链表成为一个大环的时候(头尾相连),则直接返回//再相遇即为换入口节点while (true) {if (slow == fast) {return slow;}slow = slow.next;fast = fast.next;}}}//从循环出来的不是闭环return null;}

        需要注意的是,当该链表是首尾相连时,第一次相遇时,不用再走第二次了,因为此时正好是环入口节点,直接返回当前节点。因此第一次相遇之后,将龟移到头节点处,接着就要判断此时龟与兔此时是否为同一个节点。否则,将龟移到头节点处后,没有先判断龟与兔是否为同一个节点,而将龟、兔同时走向下一步时,就会进入判断 if(slow == fast),返回已经相对与环节点的下一个的节点。

        3.2 解释为什么第一次相遇后,兔、龟每一次都走一步最终会相遇且该节点是环入口节点的原因

        假设,起点到环入口点的距离为 a 个节点,n 为在环中转的圈数,k 为在圈中走的节点数(可以理解为不够一圈的余数)。可以得出一条公式:h = a + n 无论 n 为多少,h 都会刚好来到环入口处

        那么在龟、兔第一次相遇时,对于龟来说,走了 g = a + n1 + k,对于兔来说,走了 t = a + n2 + k,对于 n1 ,n2 来说是多少都不在乎,但是两者的 k 、a 是一样的。上面说到,在第一次相遇的时候,兔所走的距离恰好是龟的距离的两倍,则龟走的距离 = 兔走的距离 - 龟走的距离,由此可得,相当与将龟走的距离换算为圈数: g = t - g = n2 - n1 g = n3,n3 具体是多少圈不在乎,反正知道是走了圈数,那么结合 a + n 永远走到的是环入口节点,那么 n3 再加上 a 是不是也会走到环入口处?

        所以此时,利用兔在与龟的第一次相遇的节点,与龟重新移回头节点处,接着龟与兔每一次走一步,知道他们相遇时所在的节点即为环入口节点。

 

        4.0 实现判断回文链表、判断环链表且寻找环入口节点的完整代码

import java.util.Iterator;public class List implements Iterable<Integer>{private final Node sentry;static class Node {public int value;public Node next;public Node() {}public Node(int value, Node next) {this.value = value;this.next = next;}@Overridepublic String toString() {return "Node{" +"value=" + value + "}";}}//外部类构造器,初始化哨兵节点public List() {sentry = new Node(-1,null);}//头插节点public void addFirst(int value) {this.sentry.next = new Node(value,this.sentry.next);}//尾插节点public void addLats(int value) {Node p = this.sentry;while (p.next != null) {p = p.next;}p.next = new Node(value,null);}//重写迭代器@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p = sentry.next;@Overridepublic boolean hasNext() {return p != null;}@Overridepublic Integer next() {int k = p.value;p = p.next;return k;}};}//查找链表中的中间的节点(快慢指针):假如为奇数,则需要找到中间的节点;// 假如是偶数,则需要找到中间的两个节点的后一个节点。public Node searchMidNode() {//判断是否为空链表if (this.sentry.next == null) {return null;}Node fast = this.sentry.next;Node slow = this.sentry.next;while (fast!= null && fast.next != null) {fast = fast.next.next;slow = slow.next;}return slow;}//判断是否为回文链表public boolean isPalindromeList() {Node p = this.sentry.next;//需要先找到中间节点Node midNode = this.searchMidNode();//然后将中间节点往后的链表进行反转,反转可以用递归的方法。Node newMidNode = reverseRecursion(midNode);//接下来就要对旧节点的前半段链表进行循环遍历来比较了每一个节点的值是否相同了//当且仅当,当迭代到反转后的链表的最后一个为 null 时,结束循环while (newMidNode != null) {if (p.value != newMidNode.value) {return false;}p = p.next;newMidNode = newMidNode.next;}return true;}//用递归实现链表反转public Node reverseRecursion(Node p) {if (p.next == null) {return p;}Node last = reverseRecursion(p.next);p.next.next = p;p.next = null;return last;}//判断是否闭环,如果是返回,则返回换入口;如果不是,则返回 nullpublic Node isLoop() {Node fast = this.sentry.next;Node slow = this.sentry.next;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) {slow = this.sentry.next;//特例:当链表成为一个大环的时候(头尾相连),则直接返回//再相遇即为换入口节点while (true) {if (slow == fast) {return slow;}slow = slow.next;fast = fast.next;}}}//从循环出来的不是闭环return null;}}

相关文章:

Java 算法篇-链表的经典算法:判断回文链表、判断环链表与寻找环入口节点(“龟兔赛跑“算法实现)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 链表的创建 2.0 判断回文链表说明 2.1 快慢指针方法 2.2 使用递归方式实现反转链表方法 2.3 实现判断回文链表 - 使用快慢指针与反转链表方法 3.0 判断环链表说明…...

【JS】Chapter13-构造函数数据常用函数

站在巨人的肩膀上 黑马程序员前端JavaScript入门到精通全套视频教程&#xff0c;javascript核心进阶ES6语法、API、js高级等基础知识和实战教程 &#xff08;十三&#xff09;构造函数&数据常用函数 1. 深入对象 1.1 创建对象三种方式 利用对象字面量创建对象const o {…...

06-流媒体-YUV数据在SDL控件显示

整体方案&#xff1a; 采集端&#xff1a;摄像头采集&#xff08;YUV&#xff09;->编码&#xff08;YUV转H264&#xff09;->写封装&#xff08;&#xff28;264转FLV&#xff09;->RTMP推流 客户端&#xff1a;RTMP拉流->解封装&#xff08;FLV转H264&#xff09…...

对象和数据结构

文章目录 前言一、从链式调用说起二、数据抽象三、数据、对象的反对称性四、得墨忒尔律五、数据传送对象总结 前言 代码整洁之道读书随笔&#xff0c;第六章 一、从链式调用说起 面向对象语言中常用的一种调用形式&#xff0c;链式调用&#xff0c;是一种较受推崇的编码风格&…...

ESP32-BLE基础知识

一、存储模式 两种存储模式&#xff1a; 大端存储&#xff1a;低地址存高字节&#xff0c;如将0x1234存成[0x12,0x34]。小端存储&#xff1a;低地址存低字节&#xff0c;如将0x1234存成[0x34,0x12]。 一般来说&#xff0c;我们看到的一些字符串形式的数字都是大端存储形式&a…...

vscode终端npm install报错

报错如下&#xff1a; npm WARN read-shrinkwrap This version of npm is compatible with lockfileVersion1, but package-lock.json was generated for lockfileVersion2. Ill try to do my best with it! npm ERR! code EPERM npm ERR! syscall open npm ERR! errno -4048…...

雪花算法的使用

雪花算法的使用(工具类utils) import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;// 雪花算法 Component public class SnowflakeUtils { // Generated ID: 1724603634882318341; // Generated ID: 1724603…...

flink源码分析之功能组件(一)-metrics

简介 本系列是flink源码分析的第二个系列,上一个《flink源码分析之集群与资源》分析集群与资源,本系列分析功能组件,kubeclient,rpc,心跳,高可用,slotpool,rest,metric,future。其中kubeclient上一个系列介绍过,本系列不在介绍。 本文介绍flink metrics组件,metric…...

Nginx反向代理和负载均衡

1.反向代理 反向代理&#xff08;Reverse Proxy&#xff09;方式是指以代理服务器来接受internet上的连接请求&#xff0c;然后将请求转发给内部网络上的服务器&#xff0c;并将从服务器上得到的结果返回给internet上请求连接的客户端&#xff0c;此时代理服务器对外就表现为一…...

基于SSM的供电公司安全生产考试系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…...

大数据-之LibrA数据库系统告警处理(ALM-12055 证书文件即将过期)

告警解释 系统每天二十三点检查一次当前系统中的证书文件&#xff0c;如果当前时间距离过期时间不足告警阈值天数&#xff0c;则证书文件即将过期&#xff0c;产生该告警。告警阈值天数的配置请参考《管理员指南》的“配置证书即将过期告警阈值”章节。 当重新导入一个正常证…...

应试教育导致学生迷信标准答案惯性导致思维僵化-移动机器人

移动机器人课程群实践创新的困境与突围 一、引言 随着科技的快速发展&#xff0c;工程教育变得越来越重要。然而&#xff0c;传统的应试教育模式往往侧重于理论知识的传授&#xff0c;忽视了学生的实践能力和创新精神的培养。这在移动机器人课程群的教学中表现得尤为明显。本文…...

【运维篇】5.4 Redis 并发延迟检测

文章目录 0.前言Redis工作原理可能引起并发延迟的常见操作和命令并发延迟检测分析和解读监控数据&#xff1a;优化并发延迟的策略 1. 检查CPU情况2. 检查网络情况3. 检查系统情况4. 检查连接数5. 检查持久化 &#xff1a;6. 检查命令执行情况 0.前言 Redis 6.0版本之前其使用单…...

碰到一个逆天表中表数据渲染

1. 逆天表中表数据问题 我有一个antd-table组件&#xff0c;他的编辑可以打开一个编辑弹窗打开弹窗里面还会有一个表格&#xff0c;如果这个表格的column是在外层js文件中保存的话&#xff0c;那么第一次打开会正常渲染数据&#xff0c;再次打开就不会渲染&#xff0c;即使是已…...

记录我常用的免费API接口

目录 1.随机中英文句子 2.随机中英文句子&#xff08;带图片和音频&#xff09; 3.随机一句诗 4.随机一句话 5.随机一句情话 6. 随机一句舔狗语录 7.历史上的今天 8.获取来访者ip地址 9&#xff1a;获取手机号信息 10. 垃圾分类查询 11.字典查询 12.QQ信息查询 1.随…...

编程的简单实例,编程零基础入门教程,中文编程开发语言工具下载

编程的简单实例&#xff0c;编程零基础入门教程&#xff0c;中文编程开发语言工具下载 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&…...

创芯科技USB_CAN【库文件】

只用到【只收】【只发】功能 23.11.18 using help; //using Models; using System; using System.Collections.Generic; using System.Linq; using System.Net.NetworkInformation; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Sys…...

React整理总结(四)

1.过渡动画react-transition-group Transition 与平台无关&#xff0c;不一定使用css实现CSSTransition组件&#xff0c;in属性控制展示隐藏&#xff0c;添加className&#xff1b;有三个状态appear | enter | exit 第一类&#xff0c;开始状态&#xff1a;对于的类是-appear、…...

ajax,axios,fetch

文章目录 ajax工作原理ajax发请求四个步骤创建xmlhttprequest对象设置请求方式设置回调函数发送请求 自封装ajax axiosaxios 特性如何用配置拦截器fetch 三者区别 ajax 工作原理 Ajax的工作原理相当于在用户和服务器之间加了—个中间层(AJAX引擎)&#xff0c;使用户操作与服务…...

Java值传递和引用传递

在Java中&#xff0c;有值传递&#xff08;Pass-by-Value&#xff09;和引用传递&#xff08;Pass-by-Reference&#xff09;两种参数传递方式。 值传递&#xff08;Pass-by-Value&#xff09;&#xff1a;当使用值传递方式时&#xff0c;方法将参数的副本传递给调用方法。这意…...

FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)

FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法&#xff08;1&#xff09; 单字节写时序单字节读时序I2C 控制器设计模块框图scl_high 和 scl_low 产生的时序图状态转移图 Verilog代码 FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法&#xff08;1&#xff09; FPG…...

LabVIEW编程开发NI-USRP

LabVIEW编程开发NI-USRP 可编程性是SDR的关键特性&#xff0c;它使人们能够将无线电外围设备转换为先进的无线系统。USRP是市场上最开放、最通用的SDR&#xff0c;可帮助工程师在主机和FPGA上使用各种软件开发工具构建系统。 有多种选项可用于对基于SDR的系统的主机进行编程。…...

ES6中实现继承

本篇文章主要说明在ES6中如何实现继承&#xff0c;学过java的小伙伴&#xff0c;对class这个关键字应该不陌生&#xff0c;ES6中也提供了class这个关键字作为实现类的语法糖&#xff0c;咱们一起实现下ES6中的继承。 实现思路 首先直接通过class来声明一个Teacther类&#xff…...

车载通信架构 —— 新车载总线类型下(以太网)的通信架构

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗你的人和事&#xff0c;多看一眼都是你的不…...

ArkTS - HarmonyOS服务卡片(创建)

可以参考官网文档 其中我们在已有的文件中File > New > Service Widget创建你想要的小卡片 本文章发布时目前可使用的模板就三种 有卡片后的new 最终效果...

Zotero在word中插入带超链接的参考文献/交叉引用/跳转参考文献

Zotero以其丰富的插件而闻名&#xff0c;使用起来十分的带劲&#xff0c;最重要的是它是免费的、不卡顿&#xff0c;不像某专业软件。 然而Zotero在word插入参考文献时&#xff0c;无法为参考文献添加超链接&#xff0c;这是一个不得不提的遗憾。 不过&#xff0c;有大佬已经…...

持续集成部署-k8s-配置与存储-配置管理:ConfigMap 的热更新

ConfigMap 的热更新 1. 简介2. 新建 Pod3. 使用 edit 命令编辑修改4. 使用 replace 命令替换修改1. 简介 在 Kubernetes 中,ConfigMap 是用于存储非敏感配置数据的 API 对象,它可以被挂载到 Pod 中作为文件或环境变量。ConfigMap 的热更新指的是在不重启 Pod 的情况下,动态…...

Python文本段落翻译

Python文本段落翻译 1、Translate库2、基本使用 1、Translate库 translate非标准库是Python中可以实现对多种语言进行互相翻译的库&#xff0c;translate可以将原始文本或段落翻译成我们需要的目标语言 translate支持多种语言&#xff0c;常见的例如&#xff1a; zh/zh-CN&…...

Flink(七)【输出算子(Sink)】

前言 今天是我写博客的第 200 篇&#xff0c;恍惚间两年过去了&#xff0c;现在已经是大三的学长了。仍然记得两年前第一次写博客的时候&#xff0c;当时学的应该是 Java 语言&#xff0c;菜的一批&#xff0c;写了就删&#xff0c;怕被人看到丢脸。当时就想着自己一年之后&…...

【自我管理】To-do list已过时?学写Done List培养事业成功感

自我管理&#xff1a;已完成清单&#xff08;doneList&#xff09;培养事业成功感 待办事项清单常常让人感到压力山大&#xff0c;让人不想面对工作。但是&#xff0c;你知道吗&#xff1f;除了待办清单之外&#xff0c;还有一个叫做「已完成清单」的东西&#xff0c;它可能更符…...