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

news2024/12/23 13:11:01

🔥博客主页: 【小扳_-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);
    }

}

         

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

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

相关文章

常见面试题-MySQL的Explain执行计划

了解 Explain 执行计划吗&#xff1f; 答&#xff1a; explain 语句可以帮助我们查看查询语句的具体执行计划。 explain 查出来的各列含义如下&#xff1a; id&#xff1a;在一个大的查询语句中&#xff0c;每个 select 关键字都对应一个唯一的 id select_type&#xff1a;…

Android手机投屏神器scrcpy

scrcpy 下装/安装 scrcpy 用于电脑控制 Android 设备的命令行工具 官方下载地址&#xff1a;https://github.com/Genymobile/scrcpy/releases 非官方下载地址 https://download.csdn.net/download/weixin_43335288/88505202 下载后直接解压&#xff08;免安装&#xff09; 连…

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

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

YOLO对象检测算法也这么卷了吗——基于YOLOv8的人体姿态检测

前期的文章我们介绍了很多关于YOLO系列的对象检测算法,虽然YOLO系列是应用在目标检测算法上,但是最近更新的YOLO系列算法都加入了对象分割,人体姿态检测等模型。 YOLOv8对象检测算法 2023年,Ultralytics再次发布YOLO更新模型,YOLOv8模型。Ultralytics YOLOv8是YOLO对象检…

[算法学习笔记](超全)概率与期望

引子 先来讲个故事 话说在神奇的OI大陆上&#xff0c;有一只paper mouse 有一天&#xff0c;它去商场购物&#xff0c;正好是11.11&#xff0c;商店有活动 它很荣幸被选上给1832抽奖 在抽奖箱里&#xff0c;有3个篮蓝球&#xff0c;12个红球 paper mouse能抽3次 蒟蒻的p…

西南科技大学814考研二

C语言数据结构与算法 线性表 顺序表(静态分配内存) #include <stdio.h> #include <stdbool.h> //静态顺序表 #define MAX_SIZE 8 //顺序表储存的数据类型 typedef int ElemType; typedef struct {ElemType data[MAX_SIZE];int length; }SeqList; //初始化顺序表…

【精选】HTML5最全知识点集合

HTML5简介与基础骨架 HTML5介绍 HTML5是用来描述网页的一种语言&#xff0c;被称为超文本标记语言。用HTML5编写的文件&#xff0c;后缀以.html结尾 HTML是一种标记语言&#xff0c;标记语言是一套标记标签。标签是由尖括号包围的关键字&#xff0c;例如&#xff1a;<html…

关于ASO优化的分步入门指南1

欢迎阅读我们的应用商店优化&#xff08;ASO&#xff09;分步指南&#xff0c;接下来我们将引导大家完成ASO研究的初始步骤&#xff0c;为提高应用程序的知名度和吸引自然下载奠定基础。 1、确定竞争对手。 首先确定应用程序的直接和间接竞争对手。我们可以通过咨询客户或进行…

RasberryPi 3B+ 树莓派 初识

关于香橙派的学习暂时告一段落&#xff0c;从本节开始学习树莓派3B&#xff01; 我在亚马逊官网购买了3b和壳子&#xff0c;安装完成后大概长这样&#xff1a; &#xff08;感觉的确像一台小型电脑主机了&#xff09; 树莓派的引脚功能图 图参&#xff1a;树莓派3B 引脚图说明…

4.3 Windows驱动开发:监控进程与线程对象操作

在内核中&#xff0c;可以使用ObRegisterCallbacks这个内核回调函数来实现监控进程和线程对象操作。通过注册一个OB_CALLBACK_REGISTRATION回调结构体&#xff0c;可以指定所需的回调函数和回调的监控类型。这个回调结构体包含了回调函数和监控的对象类型&#xff0c;还有一个A…

RK3568驱动指南|第七篇 设备树-第67章 of操作函数实验:获取属性

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

笔记54:门控循环单元 GRU

本地笔记地址&#xff1a;D:\work_file\DeepLearning_Learning\03_个人笔记\3.循环神经网络\第9章&#xff1a;动手学深度学习~现代循环神经网络 a a a a a a a

第八篇 基于JSP 技术的网上购书系统——商品信息查看、我的购物车、结算中心功能实现(网上商城、仿淘宝、当当、亚马逊)

目录 1.商品信息查看 1.1功能说明 1.2界面设计 1.3 处理流程 1.4数据来源和算法 1.4.1数据来源 1.4.2查询条件 1.4.3表间关系 1.4.4相关sql实例 2.我的购物车 2.1功能说明 2.2界面设计 2.3处理流程 2.4 数据来源和算法 3.结算中心 3.1功能说明 3.2界…

企业服务器中了babyk勒索病毒怎么办,babyk勒索病毒解密数据集恢复

网络技术的不断发展应用&#xff0c;为企业的生产生活提供了强有力帮助&#xff0c;企业也不断走向数字化办公模式&#xff0c;而对于企业来说&#xff0c;企业计算机存储的数据至关重要&#xff0c;如果不加以保护很容易造成数据丢失&#xff0c;近期&#xff0c;云天数据恢复…

JAXB的XmlElement注解

依赖 如果基于JAX-WS开发&#xff0c;可以在maven工程的pom.xml文件中增加如下依赖&#xff0c;会将依赖的JAXB库也下载下来&#xff1a; <dependency><groupId>jakarta.xml.ws</groupId><artifactId>jakarta.xml.ws-api</artifactId><vers…

网络工程师沦为IT行业里的“二等公民”了?

大家好&#xff0c;我是老杨。 都说网工难&#xff0c;都说网工苦&#xff0c;都说网工行业已经不行了、网工成为最底层、“二等公民”&#xff0c;已经彻底没落了…… 这些在互联网里持续不断散发出来的负能量&#xff0c;有没有让你在深夜耍手机的时候一次又一次的扎心过&a…

【HarmonyOS开发】配置开发工具DevEco Studio

1、下载 注意&#xff1a; 1、安装过程中&#xff0c;一定要自定义安装位置&#xff0c;包比较大&#xff0c;包比较大&#xff0c;包比较大&#xff01;&#xff01;&#xff01; 2、可以将该工具添加到右键中&#xff0c;否则&#xff0c;如果你的项目不是HarmonyOS&#xff…

数组两种初始化方法

1.数组的静态初始化 静态初始化即在初始化数组时即规定了数组的大小以及数组中每个元素的值 有三种静态初始化的方法&#xff1a; 以初始化一个int类型的数组为例&#xff1a; 1.数组类型[] 数组名 new 数组类型[]{元素1,元素2...元素n}; int[] a new int[]{1,3,5}; 2.数…

Vue3 动态设置 ref

介绍 在一些场景&#xff0c;ref设置是未知的需要根据动态数据来决定&#xff0c;如表格中的input框需要我们主动聚焦&#xff0c;就需要给每一个input设置一个ref&#xff0c;进而进行聚焦操作。 Demo 点击下面截图中的编辑按钮&#xff0c;自动聚焦到相应的输入框中。 &…

C 语言字符串

C 语言字符串 在本教程中&#xff0c;您将学习C语言编程中的字符串。您将在示例的帮助下学习声明它们&#xff0c;对其进行初始化以及将它们用于各种 I / O&#xff08;输入/输出&#xff09;操作。 在C语言编程中&#xff0c;字符串是以null字符\0结束的字符序列。例如: ch…