• 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 链表的说明

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

    代码如下:

    1. import java.util.Iterator;
    2. public class List implements Iterable{
    3. private Node sentry;
    4. static class Node {
    5. public int value;
    6. public Node next;
    7. public Node(int value, Node next) {
    8. this.value = value;
    9. this.next = next;
    10. }
    11. }
    12. public List() {
    13. sentry = new Node(-1,null);
    14. }
    15. //头插节点
    16. public void addFirst(int value) {
    17. sentry.next = new Node(value,sentry.next);
    18. }
    19. //尾插节点
    20. public void addLast( int value) {
    21. Node temp = sentry;
    22. while (temp.next != null) {
    23. temp = temp.next;
    24. }
    25. temp.next = new Node(value,null);
    26. }
    27. //重写迭代器
    28. @Override
    29. public Iterator iterator() {
    30. return new Iterator() {
    31. Node p = sentry.next;
    32. @Override
    33. public boolean hasNext() {
    34. return p != null;
    35. }
    36. @Override
    37. public Integer next() {
    38. int value = p.value;
    39. p = p.next;
    40. return value;
    41. }
    42. };
    43. }
    44. }

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

    需要了解的小伙伴可以点击该链接【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 该节点的指向进行重整

    代码如下:

    1. //去重方法一(保留):递归
    2. public List removeRecursion(List list) {
    3. Node sentry1 = list.sentry;
    4. sentry = recursion1(sentry1);
    5. return list;
    6. }
    7. private Node recursion1(Node p) {
    8. if (p == null || p.next == null) {
    9. return p;
    10. }
    11. if (p.value == p.next.value) {
    12. return recursion1(p.next);
    13. }else {
    14. p.next = recursion1(p.next);
    15. return p;
    16. }
    17. }

            需要注意的是,先得判断链表对象是否为 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

    代码如下:

    1. //去重方法二(保留):双指针
    2. public List removeDoublePointer(List list) {
    3. if (list == null) {
    4. return null;
    5. }
    6. //少于两个节点,不存在重复的值
    7. if (list.sentry.next == null || list.sentry.next.next == null) {
    8. return null;
    9. }
    10. Node n1 = list.sentry.next;
    11. Node n2;
    12. while ((n2 = n1.next) != null) {
    13. if (n2.value == n1.value) {
    14. n1.next = n2.next;
    15. }else {
    16. n1 = n1.next;
    17. }
    18. }
    19. return list;
    20. }

            需要注意的是,先得判断对象是否为 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 就是最后一个重复值的节点,因为不需要这个节点,则返回下一个节点

    代码如下:

    1. //去重方法一(不保留):递归
    2. public List removeRepeat(List list) {
    3. Node temp = list.sentry;
    4. sentry = recursion(temp);
    5. return list;
    6. }
    7. public Node recursion(Node p) {
    8. if (p == null || p.next == null) {
    9. return p;
    10. }
    11. if (p.value != p.next.value) {
    12. p.next = recursion(p.next);
    13. return p;
    14. }else {
    15. while (p.value == p.next.value) {
    16. p = p.next;
    17. }
    18. return recursion(p.next);
    19. }
    20. }

            同样的,也需要先判断该对象是否为 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)

    代码如下:

    1. //去重方法二(不保留):三指针
    2. public List removeThreePointer(List list) {
    3. if (list == null) {
    4. return null;
    5. }
    6. Node n1 = list.sentry;
    7. Node n2 ;
    8. Node n3 ;
    9. while ((n2 = n1.next) != null && (n3 = n2.next) != null) {
    10. if (n2.value == n3.value) {
    11. while (n3 != null && n2.value == n3.value) {
    12. n3 = n3.next;
    13. }
    14. n1.next = n3;
    15. }else {
    16. n1 = n1.next;
    17. }
    18. }
    19. return list;
    20. }

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

            3.0 合并升序链表

            分为两种情况:

            一、合并两个升序链表

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

            二、合并多个升序链表

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

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

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

    代码如下:

    1. //合并升序链表
    2. public static List combinedList(List l1,List l2) {
    3. if (l1 == null && l2 == null) {
    4. return null;
    5. } else if (l1 == null) {
    6. return l2;
    7. } else if (l2 == null) {
    8. return l1;
    9. }
    10. List newList = new List();
    11. Node node1 = l1.sentry.next;
    12. Node node2 = l2.sentry.next;
    13. Node p = newList.sentry;
    14. while (node1 != null && node2 != null) {
    15. if (node1.value < node2.value) {
    16. p.next = node1;
    17. node1 = node1.next;
    18. }else {
    19. p.next = node2;
    20. node2 = node2.next;
    21. }
    22. p = p.next;
    23. }
    24. if (node1 != null) {
    25. p.next = node1;
    26. }
    27. if (node2 != null) {
    28. p.next = node2;
    29. }
    30. return newList;
    31. }

            

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

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

    代码如下:

    1. private Node combineRecursion(Node p1, Node p2) {
    2. if (p1 == null) {
    3. return p2;
    4. } else if (p2 == null ) {
    5. return p1;
    6. }
    7. if (p1.value < p2.value) {
    8. p1.next = combineRecursion(p1.next,p2);
    9. return p1;
    10. }else {
    11. p2.next = combineRecursion(p1,p2.next);
    12. return p2;
    13. }
    14. }

            3.3 合并多个升序链表

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

    代码如下:

    1. //实现多个升序链表合并
    2. public List moreCombine(Node[] nodes) {
    3. List list = new List();
    4. list.sentry.next = moreCombineRecursion(nodes,0, nodes.length-1);
    5. return list;
    6. }
    7. private Node moreCombineRecursion(Node[] nodes,int i,int j) {
    8. if (j == 1) {
    9. return nodes[i];
    10. }
    11. int mid = (i + j) >>> 1;
    12. Node left = moreCombineRecursion(nodes,i,mid);
    13. Node right = moreCombineRecursion(nodes,mid+1,j);
    14. return combineRecursion(left,right);
    15. }

    举例画图分析:

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

     

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

     

    1. import java.util.Iterator;
    2. public class List implements Iterable{
    3. private Node sentry;
    4. static class Node {
    5. public int value;
    6. public Node next;
    7. public Node(int value, Node next) {
    8. this.value = value;
    9. this.next = next;
    10. }
    11. }
    12. public List() {
    13. sentry = new Node(-1,null);
    14. }
    15. //头插节点
    16. public void addFirst(int value) {
    17. sentry.next = new Node(value,sentry.next);
    18. }
    19. //尾插节点
    20. public void addLast( int value) {
    21. Node temp = sentry;
    22. while (temp.next != null) {
    23. temp = temp.next;
    24. }
    25. temp.next = new Node(value,null);
    26. }
    27. //重写迭代器
    28. @Override
    29. public Iterator iterator() {
    30. return new Iterator() {
    31. Node p = sentry.next;
    32. @Override
    33. public boolean hasNext() {
    34. return p != null;
    35. }
    36. @Override
    37. public Integer next() {
    38. int value = p.value;
    39. p = p.next;
    40. return value;
    41. }
    42. };
    43. }
    44. //去重方法二(保留):双指针
    45. public List removeDoublePointer(List list) {
    46. if (list == null) {
    47. return null;
    48. }
    49. //少于两个节点,不存在重复的值
    50. if (list.sentry.next == null || list.sentry.next.next == null) {
    51. return null;
    52. }
    53. Node n1 = list.sentry.next;
    54. Node n2;
    55. while ((n2 = n1.next) != null) {
    56. if (n2.value == n1.value) {
    57. n1.next = n2.next;
    58. }else {
    59. n1 = n1.next;
    60. }
    61. }
    62. return list;
    63. }
    64. //去重方法一(保留):递归
    65. public List removeRecursion(List list) {
    66. Node sentry1 = list.sentry;
    67. sentry = recursion1(sentry1);
    68. return list;
    69. }
    70. private Node recursion1(Node p) {
    71. if (p == null || p.next == null) {
    72. return p;
    73. }
    74. if (p.value == p.next.value) {
    75. return recursion1(p.next);
    76. }else {
    77. p.next = recursion1(p.next);
    78. return p;
    79. }
    80. }
    81. //去重方法一(不保留):递归
    82. public List removeRepeat(List list) {
    83. Node temp = list.sentry;
    84. sentry = recursion(temp);
    85. return list;
    86. }
    87. public Node recursion(Node p) {
    88. if (p == null || p.next == null) {
    89. return p;
    90. }
    91. if (p.value != p.next.value) {
    92. p.next = recursion(p.next);
    93. return p;
    94. }else {
    95. while (p.value == p.next.value) {
    96. p = p.next;
    97. }
    98. return recursion(p.next);
    99. }
    100. }
    101. //去重方法二(不保留):三指针
    102. public List removeThreePointer(List list) {
    103. if (list == null) {
    104. return null;
    105. }
    106. Node n1 = list.sentry;
    107. Node n2 ;
    108. Node n3 ;
    109. while ((n2 = n1.next) != null && (n3 = n2.next) != null) {
    110. if (n2.value == n3.value) {
    111. while (n3 != null && n2.value == n3.value) {
    112. n3 = n3.next;
    113. }
    114. n1.next = n3;
    115. }else {
    116. n1 = n1.next;
    117. }
    118. }
    119. return list;
    120. }
    121. //合并升序链表
    122. public static List combinedList(List l1,List l2) {
    123. if (l1 == null && l2 == null) {
    124. return null;
    125. } else if (l1 == null) {
    126. return l2;
    127. } else if (l2 == null) {
    128. return l1;
    129. }
    130. List newList = new List();
    131. Node node1 = l1.sentry.next;
    132. Node node2 = l2.sentry.next;
    133. Node p = newList.sentry;
    134. while (node1 != null && node2 != null) {
    135. if (node1.value < node2.value) {
    136. p.next = node1;
    137. node1 = node1.next;
    138. }else {
    139. p.next = node2;
    140. node2 = node2.next;
    141. }
    142. p = p.next;
    143. }
    144. if (node1 != null) {
    145. p.next = node1;
    146. }
    147. if (node2 != null) {
    148. p.next = node2;
    149. }
    150. return newList;
    151. }
    152. //合并链表:递归实现
    153. public List combineList(List list2) {
    154. List newList = new List();
    155. Node p1 = this.sentry.next;
    156. Node p2 = list2.sentry.next;
    157. Node p = combineRecursion(p1,p2);
    158. newList.sentry.next = p;
    159. return newList;
    160. }
    161. private Node combineRecursion(Node p1, Node p2) {
    162. if (p1 == null) {
    163. return p2;
    164. } else if (p2 == null ) {
    165. return p1;
    166. }
    167. if (p1.value < p2.value) {
    168. p1.next = combineRecursion(p1.next,p2);
    169. return p1;
    170. }else {
    171. p2.next = combineRecursion(p1,p2.next);
    172. return p2;
    173. }
    174. }
    175. //实现多个升序链表合并
    176. public List moreCombine(Node[] nodes) {
    177. List list = new List();
    178. list.sentry.next = moreCombineRecursion(nodes,0, nodes.length-1);
    179. return list;
    180. }
    181. private Node moreCombineRecursion(Node[] nodes,int i,int j) {
    182. if (j == i) {
    183. return nodes[i];
    184. }
    185. int mid = (i + j) >>> 1;
    186. Node left = moreCombineRecursion(nodes,i,mid);
    187. Node right = moreCombineRecursion(nodes,mid+1,j);
    188. return combineRecursion(left,right);
    189. }
    190. }

             

  • 相关阅读:
    VT 入门番外篇——最小 VT 实现(上)
    介绍一款 SaaS 服务器监控工具: CloudStats
    软件的开发模型
    c++ lambda 表达式
    2022年全球及中国工程机械租赁行业头部企业市场占有率及排名调研报告
    Mysql查询去空格方法汇总
    电梯的测试用例
    开发yocto系统-1
    源码编译安装部署lnmp
    mysql中DQL查询语句的使用级联操作
  • 原文地址:https://blog.csdn.net/Tingfeng__/article/details/134470372