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;}}

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

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

相关文章

设计模式-迭代器模式-笔记

动机&#xff08;Motivaton&#xff09; 在软件构建过程中&#xff0c;集合对象内部结构常常变化各异。但对于这些集合对象&#xff0c;我们呢希望在不暴露其内部结构的同时&#xff0c;可以让外部客户代码透明地访问其中包含的元素&#xff1b;同时这种“透明遍历”也为“同一…

记一次攻防实战渗透

经典开局一个登录框 由于漏洞应该还未修复。对于数据和相关网址打个码见谅一下 常规思路&#xff08;爆破&#xff09; 常规操作进行一波 尝试弱口令然后开始爆破 对于此种有验证码的爆破&#xff0c;可以借用一个bp插件。 captcha-killer-modified-jdk14.jar 具体使用我就…

游戏报错d3dcompiler_47.dll缺失怎么修复,总结多种修复方法

在使用这些软件和游戏的过程中&#xff0c;我们常常会遇到一些问题&#xff0c;其中之一就是d3dcompiler_47.dll丢失的问题。这个问题可能会导致软件或游戏无法正常运行&#xff0c;给用户带来困扰。本文将详细介绍解决软件游戏d3dcompiler_47.dll丢失的方法&#xff0c;帮助您…

地推团队怎么接一手app拉新项目?这几个接单平台可以试试看

首推平台&#xff1a;“聚量推客” 有粉丝问我&#xff1a; 我在五线小城市做地推&#xff0c;有个10人的地推团队&#xff0c;怎么接到一手靠谱的单子&#xff1f; 其实不止一个粉丝在后台问我&#xff0c;做地推、充场的人都在找单子&#xff0c;做这个行业就没有不缺项目的…

【C++】基础语法(中)

C基础语法&#xff08;中&#xff09; 文章目录 C基础语法&#xff08;中&#xff09;01数组一维数组数组初始化注意访问练习1练习2练习3普通做法&#xff1a;优化reverse函数练习4 多维数组清空数组memsetmemcpy 数组的部分由上到下&#xff0c;按规律 蛇形矩阵技巧 02 字符串…

《QT从基础到进阶·二十九》QT,opencv源码调试

有时候我们在使用VS调试程序的bug&#xff0c;但发现程序崩溃的地方并不在我们写的程序中&#xff0c;我们通过调用堆栈发现程序崩溃的地方出现在QT或者opencv等源码中&#xff0c;那么我们怎么能把断点打到这些开源库中&#xff0c;下面提供一种办法&#xff1a; 解决方案–右…

C语言——写一个函数,每调用一次这个函数,就会将num的值增加1

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>void Add(int* p) {(*p); // 的优先级高于* } int main() {int num0;Add(&num);printf("第一次调用:num %d\n",num);Add(&num);printf("第二次调用:num %d\n",num);Add(&num);p…

【Spring】之注解存取Bean对象

在本系列的上一篇文章中&#xff0c;我们已经了解了Spring的一些核心概念&#xff0c;并且还学习了Spring存取。但是我们发现在存取的过程中还是比较复杂&#xff0c;接下来我们将学习更为简单的Spring存取&#xff0c;其中涉及到的主要内容就是注解。并且在Spring家族的学习过…

搭建网关服务器实现DHCP自动分配、HTTP服务和免密登录

目录 一. 实验要求 二. 实验准备 三. 实验过程 1. 网关服务器新建网卡并改为仅主机模式 2. 修改新建网卡IP配置文件并重启服务 3. 搭建网关服务器的dhcp服务 4. 修改server2网卡配置文件重启服务并效验 5. 设置主机1的网络连接为仅主机模式 6. 给server2和网关服务器之…

开发者生态:共享知识,携手共进,共创技术辉煌

开发者生态&#xff1a;共享知识&#xff0c;携手共进&#xff0c;共创技术辉煌 在数字化时代&#xff0c;开发者是推动技术进步和创新的重要力量。他们创造着改变世界的软件和应用&#xff0c;推动着技术的边界不断向前。而在这个快速发展的时代&#xff0c;建立一个健康、活跃…

单链表相关面试题--7.链表的回文结构

/* 解题思路&#xff1a; 此题可以先找到中间节点&#xff0c;然后把后半部分逆置&#xff0c;最近前后两部分一一比对&#xff0c;如果节点的值全部相同&#xff0c;则即为回文。 */ class PalindromeList { public:bool chkPalindrome(ListNode* A) {if (A NULL || A->ne…

多媒体ffmpeg学习教程

多媒体ffmpeg 目前比较流行的音视频文件为:MP4 flv m3u8 ffmpeg ffmpeg ffplay ffprobe ffserverffmpeg -i INPUT -vf "split [main][tmp]; [tmp] cropiw:ih/2:0:0, vflip [flip];[main][flip] overlay0:H/2" OUTPUTffmpeg -i 2022.mp4 -vcodec mpeg4 -b:…

CSS---关于font文本属性设置样式总结

目录 1、color属性 2、font-size属性 3、font-weight属性 4、font-family属性 5、text-align属性 6、line-height属性 7、text-indent属性 8、letter-spacing属性 9、word-spacing属性 10、word-break属性 11、white-space属性 12、text-transform 12、writing-mo…

SchedulingConfigurer教程,怎么使用Spring自带的可扩展定时任务调度接口

简介&#xff1a; SchedulingConfigurer 是 Spring 框架中的一个接口&#xff0c;用于配置任务调度&#xff08;scheduling&#xff09;的相关设置。在 Spring 中&#xff0c;任务调度通常通过 Spring 的任务调度模块&#xff08;Task Scheduling&#xff09;来实现&#xff0c…

px、em、rem、百分比的区别

文章目录 1. 单位类型与相对基准2. 相对长度1.em2.rem3.%百分比4.vw 和 vh5.vmin 和 vmax6.vm7.ch8. ex 3. 总结 1. 单位类型与相对基准 单位名称单位类型 &#xff08;相对/绝对&#xff09;相对基准px相对屏幕像素缩放后的尺寸百分比相对font-size相对于继承&#xff0c;wid…

【Flask使用】全知识md文档,4大部分60页第3篇:状态cookie和session保持

本文的主要内容&#xff1a;flask视图&路由、虚拟环境安装、路由各种定义、状态保持、cookie、session、模板基本使用、过滤器&自定义过滤器、模板代码复用&#xff1a;宏、继承/包含、模板中特有变量和函数、Flask-WTF 表单、CSRF、数据库操作、ORM、Flask-SQLAlchemy…

互联网上门洗衣洗鞋小程序搭建

“闪站侠互联网洗护软件开发”围绕健康洗护、智能操作做出不断升级&#xff0c; 满足用户多样化的洗护需求&#xff0c;打造轻松洗衣洗鞋体验。 洗衣洗鞋专用软件&#xff0c;可以帮助洗衣店洗鞋店店主们省心高效的管理店铺&#xff0c;一次付款长期使用&#xff0e;功能基本涵…

趣学python编程 (四、数据结构和算法介绍)

数据结构和算法在编程中非常重要。数据结构是组织和存储数据的方式&#xff0c;而算法是解决问题的方法和步骤。你要挑战的蓝桥杯&#xff0c;实际也是在设计算法解决问题。其实各种编程语言都只是工具&#xff0c;而程序的核心数据结构算法。犹如练武&#xff0c;数据结构和算…

深度学习乳腺癌分类 计算机竞赛

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

【iOS】——知乎日报第五周总结

文章目录 一、评论区展开与收缩二、FMDB库实现本地持久化FMDB常用类&#xff1a;FMDB的简单使用&#xff1a; 三、点赞和收藏的持久化 一、评论区展开与收缩 有的评论没有被回复评论或者被回复评论过短&#xff0c;这时就不需要展开全文的按钮&#xff0c;所以首先计算被回复评…