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

        @Override
        public 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);
    }

    //重写迭代器
    @Override
    public Iterator<Integer> iterator() {

        return new Iterator<Integer>() {
            Node p = sentry.next;

            @Override
            public boolean hasNext() {
                return p != null;
            }

            @Override
            public 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 。

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

代码如下:

    //判断是否闭环,如果是返回,则返回换入口;如果不是,则返回 null
    public 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;
        }

        @Override
        public 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);
    }

    //重写迭代器
    @Override
    public Iterator<Integer> iterator() {

        return new Iterator<Integer>() {
            Node p = sentry.next;

            @Override
            public boolean hasNext() {
                return p != null;
            }

            @Override
            public 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;
    }


    //判断是否闭环,如果是返回,则返回换入口;如果不是,则返回 null
    public 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;
    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于人工水母算法优化概率神经网络PNN的分类预测 - 附代码

基于人工水母算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于人工水母算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于人工水母优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

vue-admin-template改变接口地址

修改登录接口 1.f12查看请求接口 模仿返回数据写接口 修改方式1 1.在env.devolopment修改 修改方式2 vue.config.js 改成本地接口地址 配置转发 后端创建相应接口&#xff0c;使用map返回相同的数据 修改前端请求路径 修改前端返回状态码 utils里面的request.js

Iceberg学习笔记(1)—— 基础知识

Iceberg是一个面向海量数据分析场景的开放表格式&#xff08;Table Format&#xff09;&#xff0c;其设计的目的是解决数据存储和计算引擎之间的适配的问题 表格式&#xff08;Table Format&#xff09;可以理解为元数据以及数据文件的一种组织方式&#xff0c;处于计算框架&…

Positive Technologies 利用 PT Cloud Application Firewall 保护中小型企业的网络资源

云产品按月订购&#xff0c;无需购买硬件资源 PT Cloud Application Firewall 是 Positive Technologies 推出的首个用于保护网络应用程序的商用云产品。Web 应用层防火墙 (web application firewall, WAF) 现在可以通过 技术合作伙伴——授权服务商和云提供商以订购方式提供1…

浅析ChatGPT中涉及到的几种技术点

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

PHPmail 发送邮件错误 550 的原因是什么?

电子邮件错误消息链接到简单邮件传输协议 (SMTP)&#xff0c;这是一组发送和接收电子邮件的标准化规则。因此&#xff0c;它也称为 SMTP 550 错误代码。在某些情况下&#xff0c;电子邮件错误 550 是由收件人一方的问题引起的。 以下是电子邮件错误 550 的一些可能原因&#x…

华为数通HCIP 821BGP 知识点整理

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

苹果(Apple)公司的新产品开发流程(一)

目录 简介 ANPP CSDN学院推荐 作者简介 简介 苹果这家企业给人的长期印象就是颠覆和创新。 而流程跟创新似乎是完全不搭边的两个平行线&#xff1a; 流程是一个做事的标准&#xff0c;定义了权力的边界&#xff0c;对应人员按章办事&#xff1b;而创新的主旋律是发散&am…

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

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

【代码随想录】算法训练计划27

回溯 1、39. 组合总和 题目&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的…

力扣C++学习笔记——C++ assign全面解析

cassign是一个C20标准中新增的头文件&#xff0c;主要提供了assign函数&#xff0c;用于将一个容器内的元素按照特定规则赋值到另一个容器中。它是STL容器操作的重要一环&#xff0c;具有高效、简洁、易用的特点。 assign函数有多个版本&#xff0c;一般使用的是容器类型相同或…

CSDN每日一题学习训练——Python版(N皇后 II、买卖股票的最佳时机 II、编程通过键盘输入每一位运动员)

版本说明 当前版本号[20231120]。 版本修改说明20231120初版 目录 文章目录 版本说明目录N皇后 II题目解题思路代码思路参考代码 买卖股票的最佳时机 II题目解题思路代码思路参考代码 编程通过键盘输入每一位运动员题目解题思路代码思路参考代码 N皇后 II 题目 n 皇后问题…

uvm环境获取系统时间的方法和使用案例

背景&#xff1a; 有时候我们想统计一下验证环境中某个步骤总共花费了多少时间&#xff0c;有什么比较方便的方法呢&#xff0c;利用$realtime理论上也是能做到的&#xff0c;不过这个和timescale绑定起来了&#xff0c;需要手动换算成单位是秒的数&#xff0c;现在提供一种利用…

最强英文开源模型Llama2架构与技术细节探秘

prerequisite: 最强英文开源模型LLaMA架构探秘&#xff0c;从原理到源码 Llama2 Meta AI于2023年7月19日宣布开源LLaMA模型的二代版本Llama2&#xff0c;并在原来基础上允许免费用于研究和商用。 作为LLaMA的延续和升级&#xff0c;Llama2的训练数据扩充了40%&#xff0c;达到…

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…

Python如何实现原型设计模式?什么是原型设计模式?Python 原型设计模式示例代码

什么是原型&#xff08;ProtoType&#xff09;设计模式&#xff1f; 原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;旨在通过复制现有对象来创建新对象&#xff0c;而无需通过标准的构造方式。它允许我们基于现有对象创建新对象&#xf…

数电实验-----实现74LS153芯片扩展为8选1时间选择器以及应用(Quartus II )

目录 一、74LS153芯片介绍 管脚图 功能表 二、4选1选择器扩展为8选1选择器 1.扩展原理 2.电路图连接&#xff08;Quartus II &#xff09; 3.仿真结果 三、8选1选择器的应用 1.三变量表决器 2.奇偶校验电路 一、74LS153芯片介绍 74ls153芯片是属于四选一选择器的芯片。…

你听说过“消费多少返利多少的”模式吗?

今天分享一个新的销售套路&#xff0c;看懂套路奋斗节约3年&#xff0c;你听说过“消费多少返利多少的”模式吗&#xff1f; 消费报销模式就是消费者在平台的消费&#xff0c;根据贡献度和活跃度平台去把之前消费的模式&#xff0c;给你返本了甚至还额外给你补贴奖励&#xff…

BP神经网络原理与如何实现BP神经网络

本文部分图文来自《老饼讲解-BP神经网络》bp.bbbdata.com 目录 一、BP神经网络的背景生物学原理 二、BP神经网络模型 2.1 BP神经网络的结构 2.2 BP神经网络的激活函数 三、BP神经网络的误差函数 四、BP神经网络的训练 4.1 BP神经网络的训练流程 4.2 BP神经网络的训练流…

1.索引的本质

索引是帮组MYSQL高效获取数据的排好序的数据结构 二叉树 二叉树是树节点的度不大于2的有序树。它是一种最简单最重要的树。 二叉树的左节点始终小于父节点。二叉树的有节点始终大于等于父节点 对于单边递增的数据&#xff0c;二叉树会变成链表的形式。这个时候查询不会减少次数…