Java 算法篇-链表的经典算法:有序链表去重、合并多个有序链表

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

 

 

文章目录

         1.0 链表的说明

         2.0 有序链表去重的实现方式

        2.1 有序链表去重(保留重复的节点) - 使用递归来实现

        2.2 有序链表去重(保留重复的节点) - 使用双指针来实现

        2.3 有序链表去重(不保留重复的节点) - 使用递归来实现

        2.4 有序链表去重(不保留重复的节点) - 使用三指针来实现

        3.0 合并升序链表

        3.1 合并升序链表(两个链表) - 迭代法

        3.2 合并升序链表(两个链表) - 递归法

        3.3 合并多个升序链表

        4.0 实现有序链表去重、合并升序链表的完整代码


        1.0 链表的说明

        为了更好的讲解本篇当中的两种经典算法,先创建一个带哨兵的链表。链表是一种常见的数据结构,用于存储一系列元素。链表由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针

代码如下:

import java.util.Iterator;

public class List implements Iterable<Integer>{
    private  Node sentry;

    static class Node {
        public int value;
        public Node next;

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }

    public List() {
        sentry = new Node(-1,null);
    }

    //头插节点
    public void addFirst(int value) {
        sentry.next = new Node(value,sentry.next);
    }

    //尾插节点
    public void addLast( int value) {
        Node temp = sentry;
        while (temp.next != null) {
            temp = temp.next;
        }
        temp.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 value = p.value;
                p = p.next;
                return value;
            }
        };
    }

}

        简单讲解一下,创建了一个链表类,该类中包含一个静态内部类,即节点类,还实现了一个基本的方法:头插节点、尾插节点、重写了迭代器等等。

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

         2.0 有序链表去重的实现方式

        在此之前,需要分为两个方向:

        一、需要保留重复值的节点

        使用递归来实现有序链表的去重、使用双指针来实现有序链表的去重。

        二、不需要保留重复值的节点

        使用递归来实现有序链表的去重、使用三指针来实现有序链表的去重。

        2.1 有序链表去重(保留重复的节点) - 使用递归来实现

        具体思路:先来考虑终止递出的条件为:p == null 或者 p.next == null ,对于 p == null 情况,当该链表为空链表时,直接返回 null ,对于 p.next == null 情况,当递出到最后只剩一个时,也没有必要继续下去了,不会再有重复的值的节点了。再来考虑递出的具体过程:当 p.value == p.next.value 的情况,就该忽略该节点,则需要返回下一个节点;当 p.value != p.next.value 的情况,就需要返回该节点,但是在返回之前,需要对 p.next 该节点的指向进行重整

代码如下:

    //去重方法一(保留):递归
    public List removeRecursion(List list) {
        Node sentry1 = list.sentry;
        sentry = recursion1(sentry1);
        return list;
    }

    private Node recursion1(Node p) {

        if (p == null || p.next == null) {
            return p;
        }
        if (p.value == p.next.value) {
            return recursion1(p.next);
        }else {
            p.next = recursion1(p.next);
            return p;
        }

    }

        需要注意的是,先得判断链表对象是否为 null ,不然会空指针异常。

        2.2 有序链表去重(保留重复的节点) - 使用双指针来实现

        具体思路:定义两个指针 n1 与 n2 ,对于 n1 来说:n1 一开始指向头节点,假如指向哨兵节点时,那么后续就会掺入了哨兵节点的值来比较,因此,n1 一开始时需要指向头节点。对于 n2 来说,n2 = n1.next ,也就是 n2 在 n1 指向的节点的前一个节点。接下来:当 n1.value == n2.value 时,则将 n1.next = n2.next ;当 n1.value != n2.value 时,则 n1 = n1.next 。     

        循环的条件为:(n2 = n1.next) != null

代码如下:

    //去重方法二(保留):双指针
    public List removeDoublePointer(List list) {
        if (list == null) {
            return null;
        }

        //少于两个节点,不存在重复的值
        if (list.sentry.next == null || list.sentry.next.next == null) {
            return null;
        }

        Node n1 = list.sentry.next;
        Node n2;

        while ((n2 = n1.next) != null) {

            if (n2.value == n1.value) {
                n1.next = n2.next;
            }else {
                n1 = n1.next;
            }
        }

        return list;
    }

        需要注意的是,先得判断对象是否为 null ;还有一种情况,当节点少于两个时,不存在重复的值的节点。

        2.3 有序链表去重(不保留重复的节点) - 使用递归来实现

        具体思路:先来考虑递出的终止条件为:当 p == null 或者 p.next == null 的情况时,直接返回 p 该节点,因为当 p.next == null 时,不存在有两个重复值的节点,因此就没有必要再继续递归下去了。再来考虑递出的两种情况:当 p.value != p.next.value 时,没有重复,则返回当前节点 p ,但是在此之前,需要对 p.next 重新赋值,即重新调整 p.next 的指向;当 p.value == p.next.vaule 时,存在重复,则将该值的节点全部找出来,直到找到最后一个节点。循环的条件为: p.value == p.next.value ,循环结束后,得到的 p 就是最后一个重复值的节点,因为不需要这个节点,则返回下一个节点

代码如下:

    //去重方法一(不保留):递归
    public List removeRepeat(List list) {
        Node temp = list.sentry;
        sentry = recursion(temp);
        return list;
    }

    public Node recursion(Node p) {
        if (p == null || p.next == null) {
            return p;
        }

        if (p.value != p.next.value) {
            p.next = recursion(p.next);
            return p;
        }else {
            while (p.value == p.next.value) {
                p = p.next;
            }
            return recursion(p.next);
        }

    }

        同样的,也需要先判断该对象是否为 null ,否则容易报异常。

        2.4 有序链表去重(不保留重复的节点) - 使用三指针来实现

        具体思路:先定义三个指针,对于 p1 来说: 一开始时指向哨兵节点,假如不实现哨兵节点,则删除不了当链表中前几个为重复值的节点(比如:1->1->1->2->null) ,因此,需要实现哨兵来完成该需求对与 p2 来说:一开始时指向头节点,即 p1.next;对于 p3 来说:一开始时指向头节点的下一个节点,即 p2.next 。接下来,对于循环的两种过程来分析:当 p2.value == p3.value 时,需要接着找到两个节点的值不相等的时候,所以内层循环条件为:p2.value == p3.value 且 p3 != null,这里需要特别注意的是,千万不能丢了 p3 != null 的限制条件。跳出内层循环是,就可能意味着找到了,则将 p1.next = p3 ;当 p2.value != p3.value 时,直接 p1 = p1.next 即可。外层循环的条件为:((p2 = p1.next) != null 且 (p3 = p2.next) != null)

代码如下:

    //去重方法二(不保留):三指针
    public List removeThreePointer(List list) {

        if (list == null) {
            return null;
        }

        Node n1 = list.sentry;
        Node n2 ;
        Node n3 ;
        while ((n2 = n1.next) != null && (n3 = n2.next) != null) {
            if (n2.value == n3.value) {
                while (n3 != null && n2.value == n3.value) {
                    n3 = n3.next;
                }
                n1.next = n3;
            }else {
                n1 = n1.next;
            }
        }
        return list;
    }

        这里有个小技巧,对与 p2、p3 来说,不着急赋值,在判断条件的时候再进行赋值,可以简略代码量,提高可读性。

        3.0 合并升序链表

        分为两种情况:

        一、合并两个升序链表

        使用迭代法实现合并链表、使用递归实现合并链表

        二、合并多个升序链表

        合并多个升序链表就是一个个合并两个升序链表的情况,用递归来实现

        3.1 合并升序链表(两个链表) - 迭代法

        具体思路:对于两个链表合并来说,在各自的链表中分别定义一个指针,分别指向各自的头节点。合并一条新的链表,定义一个指针指向哨兵节点。

代码如下:

    //合并升序链表
    public static List combinedList(List l1,List l2) {
        if (l1 == null && l2 == null) {
            return null;
        } else if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        }

        List newList = new List();
        Node node1 = l1.sentry.next;
        Node node2 = l2.sentry.next;
        Node p = newList.sentry;

        while (node1 != null && node2 != null) {

            if (node1.value < node2.value) {
                p.next = node1;
                node1 = node1.next;
            }else {
                p.next = node2;
                node2 = node2.next;
            }
            p = p.next;
        }

        if (node1 != null) {
            p.next = node1;
        }
        if (node2 != null) {
            p.next = node2;
        }
        return newList;
    }

        

        3.2 合并升序链表(两个链表) - 递归法

        具体思路:先来考虑递出的终止条件为:当 p1 == null 时,则直接返回 p2当 p2 == null 时,则直接返回 p1。再来考虑递出的过程:当 p1.value < p2.value 时,返回的节点为 p1 节点,在返回节点之前,需要将 p1.next 对该节点的重新调整指向下一个节点当 p1.value >= p2.value 时,返回的节点为 p2 节点,同理,在返回该节点之前,需要将 p2.next 对该节点的重新调整指向下一个节点

代码如下:

     private Node combineRecursion(Node p1, Node p2) {
        if (p1 == null) {
            return p2;
        } else if (p2 == null ) {
            return p1;
        }

        if (p1.value < p2.value) {
            p1.next = combineRecursion(p1.next,p2);
            return p1;
        }else {
            p2.next = combineRecursion(p1,p2.next);
            return p2;
        }

     }

        3.3 合并多个升序链表

        具体思路:这是一个多路递归,在每一次的递归过程中,都可以看成有两个升序链表进行来合并。

代码如下:

     //实现多个升序链表合并
    public List moreCombine(Node[] nodes) {
        List list = new List();
        list.sentry.next = moreCombineRecursion(nodes,0, nodes.length-1);
        return list;
    }

    private Node moreCombineRecursion(Node[] nodes,int i,int j) {

        if (j == 1) {
            return nodes[i];
        }
        int mid = (i + j) >>> 1;
        Node left = moreCombineRecursion(nodes,i,mid);
        Node right = moreCombineRecursion(nodes,mid+1,j);
        return combineRecursion(left,right);
    }

举例画图分析:

        对以上的流程图简单分析:注意的是结束递出的条件为:i == j 结束递出,开始回归。回归的是每一个链表的节点,最后集齐了两个链表,需要通过利用两个链表升序合并的返回进行合并,可以用迭代法或者递归法。这只是其中的一小部分,不过每一个过程都是一样的,就不多赘述了。

 

        4.0 实现有序链表去重、合并升序链表的完整代码

 

import java.util.Iterator;

public class List implements Iterable<Integer>{
    private  Node sentry;

    static class Node {
        public int value;
        public Node next;

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }

    public List() {
        sentry = new Node(-1,null);
    }

    //头插节点
    public void addFirst(int value) {
        sentry.next = new Node(value,sentry.next);
    }

    //尾插节点
    public void addLast( int value) {
        Node temp = sentry;
        while (temp.next != null) {
            temp = temp.next;
        }
        temp.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 value = p.value;
                p = p.next;
                return value;
            }
        };
    }

    //去重方法二(保留):双指针
    public List removeDoublePointer(List list) {
        if (list == null) {
            return null;
        }

        //少于两个节点,不存在重复的值
        if (list.sentry.next == null || list.sentry.next.next == null) {
            return null;
        }

        Node n1 = list.sentry.next;
        Node n2;

        while ((n2 = n1.next) != null) {

            if (n2.value == n1.value) {
                n1.next = n2.next;
            }else {
                n1 = n1.next;
            }
        }

        return list;
    }


    //去重方法一(保留):递归
    public List removeRecursion(List list) {
        Node sentry1 = list.sentry;
        sentry = recursion1(sentry1);
        return list;
    }

    private Node recursion1(Node p) {

        if (p == null || p.next == null) {
            return p;
        }
        if (p.value == p.next.value) {
            return recursion1(p.next);
        }else {
            p.next = recursion1(p.next);
            return p;
        }

    }



    //去重方法一(不保留):递归
    public List removeRepeat(List list) {
        Node temp = list.sentry;
        sentry = recursion(temp);
        return list;
    }

    public Node recursion(Node p) {
        if (p == null || p.next == null) {
            return p;
        }

        if (p.value != p.next.value) {
            p.next = recursion(p.next);
            return p;
        }else {
            while (p.value == p.next.value) {
                p = p.next;
            }
            return recursion(p.next);
        }

    }


    //去重方法二(不保留):三指针
    public List removeThreePointer(List list) {

        if (list == null) {
            return null;
        }

        Node n1 = list.sentry;
        Node n2 ;
        Node n3 ;
        while ((n2 = n1.next) != null && (n3 = n2.next) != null) {
            if (n2.value == n3.value) {
                while (n3 != null && n2.value == n3.value) {
                    n3 = n3.next;
                }
                n1.next = n3;
            }else {
                n1 = n1.next;
            }
        }
        return list;
    }


    //合并升序链表
    public static List combinedList(List l1,List l2) {
        if (l1 == null && l2 == null) {
            return null;
        } else if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        }

        List newList = new List();
        Node node1 = l1.sentry.next;
        Node node2 = l2.sentry.next;
        Node p = newList.sentry;

        while (node1 != null && node2 != null) {

            if (node1.value < node2.value) {
                p.next = node1;
                node1 = node1.next;
            }else {
                p.next = node2;
                node2 = node2.next;
            }
            p = p.next;
        }

        if (node1 != null) {
            p.next = node1;
        }
        if (node2 != null) {
            p.next = node2;
        }
        return newList;
    }

    //合并链表:递归实现
    public List combineList(List list2) {
        List newList = new List();
        Node p1 = this.sentry.next;
        Node p2 = list2.sentry.next;
        Node p = combineRecursion(p1,p2);
        newList.sentry.next = p;
        return newList;
    }

     private Node combineRecursion(Node p1, Node p2) {
        if (p1 == null) {
            return p2;
        } else if (p2 == null ) {
            return p1;
        }

        if (p1.value < p2.value) {
            p1.next = combineRecursion(p1.next,p2);
            return p1;
        }else {
            p2.next = combineRecursion(p1,p2.next);
            return p2;
        }

     }

     //实现多个升序链表合并
    public List moreCombine(Node[] nodes) {
        List list = new List();
        list.sentry.next = moreCombineRecursion(nodes,0, nodes.length-1);
        return list;
    }

    private Node moreCombineRecursion(Node[] nodes,int i,int j) {

        if (j == i) {
            return nodes[i];
        }
        int mid = (i + j) >>> 1;
        Node left = moreCombineRecursion(nodes,i,mid);
        Node right = moreCombineRecursion(nodes,mid+1,j);
        return combineRecursion(left,right);
    }

}

         

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

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

相关文章

U盘如何自定义图标?

1、准备一张图片&#xff0c;转换为.ico格式&#xff0c;转换格式的工具推荐一个ToYcon 转换好后放到拷贝到u盘里面。 2、在u盘里面新建一个文本文档&#xff0c;在文档里面写入以下内容&#xff0c;注意&#xff0c;这里的test为图片的名称。 根据自己图片名称做一下修改。 […

三十二、W5100S/W5500+RP2040树莓派Pico<UPnP示例>

文章目录 1 前言2 简介2 .1 什么是UPnP&#xff1f;2.2 UPnP的优点2.3 UPnP数据交互原理2.4 UPnP应用场景 3 WIZnet以太网芯片4 UPnP示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 随着智能家居、物联网等…

6.7二叉树的最小深度(LC111)

审题要清楚&#xff1a; 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点&#xff08;左右孩子都为空的节点才是叶子节点&#xff01;&#xff09;。 算法&#xff1a; 既可以求最小高度&#xff0c;也可以直接求深度。 最小高度&#xff1a; 后序…

4M防错追溯与MES管理系统的融合应用

在现代化制造业中&#xff0c;质量追溯已成为企业核心竞争力的重要组成部分。为了实现精确的质量追溯&#xff0c;制造企业广泛采用了MES管理系统解决方案来进行生产过程中的数据管理。本文将探讨如何通过MES管理系统实现4M防错追溯&#xff0c;并提升企业的生产与管理效率。 一…

软件质量保护与测试(第2版)学习总结第十三章 集成测试

很多人都认为微软是一家软件开发公司&#xff0c;事实上我们是一家软件测试公司。 ---比尔盖茨 集成测试是在单元测试的基础上将多个模块组合在一起进行测试的过程。 13.1.1 区别 单元测试主要关注模块内部&#xff0c;系统测试则是在用户的角度来评价系统&#xff…

第四篇 《随机点名答题系统》——基础设置详解(类抽奖系统、在线答题系统、线上答题系统、在线点名系统、线上点名系统、在线考试系统、线上考试系统)

目录 1.功能需求 2.数据库设计 3.流程设计 4.关键代码 4.1.设置题库 4.1.1数据请求示意图 4.1.2选择题库&#xff08;index.php&#xff09;数据请求代码 4.1.3取消题库&#xff08;index.php&#xff09;数据请求代码 4.1.4业务处理Service&#xff08;xztk.p…

【高并发内存池】第一篇 项目简介及定长内存池

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

Flume学习笔记(4)—— Flume数据流监控

前置知识&#xff1a; Flume学习笔记&#xff08;1&#xff09;—— Flume入门-CSDN博客 Flume学习笔记&#xff08;2&#xff09;—— Flume进阶-CSDN博客 Flume 数据流监控 Ganglia 的安装与部署 Ganglia 由 gmond、gmetad 和 gweb 三部分组成。 gmond&#xff08;Ganglia …

MybatisPlus学习

一.快速入门 1.相关数据库创建 CREATE TABLE USER(id BIGINT(20) NOT NULL COMMENT 主键ID,NAME VARCHAR(30) NULL DEFAULT NULL COMMENT 姓名,age INT(11) NULL DEFAULT NULL COMMENT 年龄,email VARCHAR(50) NULL DEFAULT NULL COMMENT 邮箱,PRIMARY KEY (id));​​INSERT I…

BUG:编写springboot单元测试,自动注入实体类报空指针异常

原因:修饰测试方法的Test注解导入错误 造成错误的原因是 import org.junit.Test;正确的应该是 import org.junit.jupiter.api.Test前者是Junit4,后者是Junit5 junit4的使用似乎要在测试类除了添加SpringbootTest还要添加RunWith(SpringRunner.class) 同时要注意spring-boot-s…

制作翻页电子相册,这个工具你必须了解!

电子相册作为一种很有纪念意义的载体&#xff0c;无论是生日、旅行、结婚、毕业纪念等等&#xff0c;可以应用在很多场合当中&#xff0c;如何制作呢&#xff1f; 而对于不会制作电子相册的人来说&#xff0c;使用套用模板是最直接快速的方式了。所以&#xff0c;推荐大家使用…

Find My充电宝|苹果Find My技术与充电宝结合,智能防丢,全球定位

充电宝是一种个人可随身携带&#xff0c;自身能储备电能&#xff0c;主要为手持式移动设备等消费电子产品&#xff08;例如无线电话、笔记本电脑&#xff09;充电的便携充电器&#xff0c;特别应用在没有外部电源供应的场合。其主要组成部分包括&#xff1a;用作电能存储的电池…

C++软件开发面试场景题

自己在秋招过程中遇到的一些场景题 海量数据N取Top K个元素&#xff0c;复杂度是多少 在处理海量数据中获取前K个元素&#xff08;Top K&#xff09;的问题中&#xff0c;通常会使用一些高效的算法来减少时间和空间复杂度。以下是两种常见的解决方案和它们的复杂度&#xff1…

LeetCode热题100——图论

图论 1. 岛屿的数量2. 腐烂的橘子 1. 岛屿的数量 给你一个由 ‘1’&#xff08;陆地&#xff09;和 ‘0’&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆…

PHP排序sort()、asort() 和 ksort() 的区别及用法

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师…

Centos(Linux)服务器安装Dotnet8 及 常见问题解决

1. 下载dotnet8 sdk 下载 .NET 8.0 SDK (v8.0.100) - Linux x64 Binaries 拿到 dotnet-sdk-8.0.100-linux-x64.tar.gz 文件 2. 把文件上传到 /usr/local/software 目录 mkdir -p /usr/local/software/dotnet8 把文件拷贝过去 mv dotnet-sdk-8.0.100-linux-x64.tar.gz /usr/loc…

多维时序 | MATLAB实现PSO-BiGRU-Attention粒子群优化双向门控循环单元融合注意力机制的多变量时间序列预测

多维时序 | MATLAB实现PSO-BiGRU-Attention粒子群优化双向门控循环单元融合注意力机制的多变量时间序列预测 目录 多维时序 | MATLAB实现PSO-BiGRU-Attention粒子群优化双向门控循环单元融合注意力机制的多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 …

混沌系统在图像加密中的应用(基于哈密顿能量函数的混沌系统构造1.4)

混沌系统在图像加密中的应用&#xff08;基于哈密顿能量函数的混沌系统构造1.4&#xff09; 前言一、逆时间对称性分析二、具有逆时间对称性的单晶格状混沌与拟周期流1.逆时间对称性及哈密顿能量函数2.数值仿真 python代码 前言 续接混沌系统在图像加密中的应用&#xff08;基…

数据科学家应该知道的 10 个高级深度学习架构!

一、介绍 跟上深度学习的最新进展变得非常困难。几乎每天都会出现深度学习的新创新或新应用。然而&#xff0c;这些进步大部分都隐藏在 ArXiv / Springer 等媒体上发表的大量研究论文中。 本文包含深度学习的一些最新进展以及 keras 库中的实现代码。我还提供了…

批量替换WordPress文章内图片链接

在WordPress使用过程中&#xff0c;如果中途更换了域名&#xff0c;原先文章内的图片使用的是原来的域名&#xff0c;就会造成文章页里面的图片链接无法显示。如果从后台文章挨个修改就比较麻烦。可以通过数据库进行批量替换即可。 使用 PHPMyadmin 打开 数据库&#xff0c;登…