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

这篇具有很好参考价值的文章主要介绍了Java 算法篇-链表的经典算法:判断回文链表、判断环链表与寻找环入口节点(“龟兔赛跑“算法实现)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

 

文章目录

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

}

Java 算法篇-链表的经典算法:判断回文链表、判断环链表与寻找环入口节点(“龟兔赛跑“算法实现),Java 数据结构与算法篇,java,算法,链表文章来源地址https://www.toymoban.com/news/detail-751782.html

到了这里,关于Java 算法篇-链表的经典算法:判断回文链表、判断环链表与寻找环入口节点(“龟兔赛跑“算法实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言经典算法实例4:判断回文数

    判断回文数 问题的描述 如下几点所示 “回文”是指正读反读都能读通的句子,它是古今中外都有的一种修辞方式和文字游戏,如“我为人人,人人为我”等。 在数学中也有这样一类数字有这样的特征,成为回文数(palindrome number)。 设n是一任意自然数,若将n的各位数字反

    2024年02月02日
    浏览(35)
  • [Collection与数据结构] 链表与LinkedList (一):链表概述与单向无头非循环链表实现

    上篇文章我们已经对顺序表进行了实现,并且对ArrayList进行了使用,我们知道ArrayList底层是使用数组实现的. 由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时, 就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低 ,因此ArrayList不适合做

    2024年04月26日
    浏览(62)
  • 顺序表与链表的区别

    目录 一、顺序表和链表的比较 顺序表 优点: 缺点: 链表 优点 缺点 二、顺序表和链表的区别 1.顺序表和链表都具有增、删、查、改功能,但算法复杂度却不相同。 2、从数据元素存储的内存角度来看 三、顺序表与链表选取方案 顺序表的特点是逻辑上相邻数据元素,物理存

    2024年02月08日
    浏览(108)
  • 算法通关村第一关——链表经典问题之寻找两个链表的第一个公共结点

    这是一道经典的链表问题,来自剑指offer52,题目是这样的:输入两个链表,找出它们的第一个公共结点,如下图所示: 两个链表的头结点均已知,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。 第一

    2024年02月16日
    浏览(55)
  • 算法通关村第一关---链表经典问题之两个链表的第一个公共节点笔记

    源码地址:GitHub-算法通关村 1.hash 2.集合 3.栈 4.双指针

    2024年02月16日
    浏览(43)
  • 【编织时空四:探究顺序表与链表的数据之旅】

    链表的分类 带头双向循环链表接口实现 顺序表和链表的区别 缓存利用率参考存储体系结构 以及 局部原理性。 实际中链表的结构非常多样,以下情况组合起来就有8种链表结构: 1. 单向或者双向 2. 带头或者不带头 3. 循环或者非循环  虽然有这么多的链表的结构,但是我们实

    2024年02月12日
    浏览(54)
  • 【编织时空三:探究顺序表与链表的数据之旅】

    链表OJ题 思路一:删除头结点时另做考虑(由于头结点没有前一个结点) 思路二:添加一个虚拟头结点,删除头结点就不用另做考虑 思路:通过三个指针的操作,每次将当前节点反转并向前移动 ​ 思路:头插法 思路:快慢指针的前进方向相同,且它们步伐的「差」是恒定

    2024年02月11日
    浏览(67)
  • 链表的极致——带头双向循环链表

    ​ 双向带头循环链表是链表结构最复杂,但使用最方便的链表。 [图中phead表示链表的头节点(哨兵); d1,d2等表示链表存储的数据; d1,d2等左侧的方框存储是指向该节点的上一个节点的指针(prev),右侧方框存储指向该节点的下一个的指针(next)] 双向带头循环链表的每

    2024年04月10日
    浏览(75)
  • 链表的回文结构

    对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针 A ,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。 测试样例: 1-2-2-1 返回:true 题目链接:链表的回文结构_牛客题霸_牛客网 

    2024年02月06日
    浏览(50)
  • 详解链表oJ<反转链表,链表的中间节点及链表的回文>

    hello,大家好,这里是Dark FlameMaster,今天和大家分享的是有关数据结构链表的几道题目,链表的中间节点,反转链表及判断链表是否为回文结构,放在一起讲解会印象更加深刻。 链接:链表的中间节点 分析:  如果想要得到链表的中间节点,最简单的思路就是从头结点遍历整

    2024年02月08日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包