【LeetHOT100】合并两个有序链表——Java多解法详解

一、题目描述

21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:

输入:list1 = [1,2,4]list2 = [1,3,4]

输出:[1,1,2,3,4,4]

示例 2:

输入:list1 = []list2 = []

输出:[]

示例 3:

输入:list1 = []list2 = [0]

输出:[0]

提示:

  • 两个链表的节点数目范围是 [0, 50]

  • -100 <= Node.val <= 100

  • list1list2 均按 非递减顺序 排列

二、解题思路概览

合并两个有序链表,最自然的想法是使用双指针归并。常见解法有三种:

解法 时间复杂度 空间复杂度 特点
迭代归并 O(m+n) O(1) 最优解,面试首选
递归归并 O(m+n) O(m+n)(栈空间) 代码简洁,思路清晰
集合排序法 O((m+n) log(m+n)) O(m+n) 不推荐,仅作对比

三、解法一:迭代归并(推荐)⭐

3.1 思路

同时遍历两个链表,比较当前节点值,将较小者接入结果链表,并移动对应链表的指针。最后将未遍历完的链表直接接到结果末尾。

3.2 代码实现

java

复制代码
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        // 哑结点,简化边界处理
        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;
        
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                cur.next = list1;
                list1 = list1.next;
            } else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        
        // 连接剩余部分
        cur.next = (list1 != null) ? list1 : list2;
        
        return dummy.next;
    }
}

3.3 图解示例

list1 = [1,2,4]list2 = [1,3,4] 为例:

text

复制代码
初始: list1: 1 → 2 → 4
      list2: 1 → 3 → 4
      dummy → null

第1步: 比较1和1,取list1的1 → dummy → 1, list1指向2
第2步: 比较2和1,取list2的1 → dummy → 1 → 1, list2指向3
第3步: 比较2和3,取list1的2 → ... → 2, list1指向4
第4步: 比较4和3,取list2的3 → ... → 3, list2指向4
第5步: 比较4和4,取list1的4 → ... → 4, list1指向null
最后: 将list2剩余的4接上 → 得到 1→1→2→3→4→4

3.4 复杂度分析

  • 时间复杂度:O(m+n),每个节点恰好被处理一次。

  • 空间复杂度:O(1),只使用了常数个指针。

四、解法二:递归归并

4.1 思路

递归定义:合并两个链表的结果 = 较小头节点 + 合并剩余部分。递归终止条件:任一链表为空,返回另一个链表。

4.2 代码实现

java

复制代码
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if (list1 == null) return list2;
        if (list2 == null) return list1;
        
        if (list1.val <= list2.val) {
            list1.next = mergeTwoLists(list1.next, list2);
            return list1;
        } else {
            list2.next = mergeTwoLists(list1, list2.next);
            return list2;
        }
    }
}

4.3 复杂度分析

  • 时间复杂度:O(m+n),每个节点被递归调用一次。

  • 空间复杂度:O(m+n),递归调用栈的深度等于合并后链表的长度。

4.4 递归与迭代对比

维度 迭代 递归
代码长度 稍长 极简
空间效率 O(1) O(n)
可读性 直观 需要理解递归
风险 链表过长可能栈溢出

五、解法三:集合排序法(不推荐)

5.1 思路

将两个链表的所有节点值取出放入 ArrayList,排序后再重新构建链表。这种方法完全没有利用链表已有序的特性,效率低下,仅作为错误代码对照。

5.2 常见错误写法及修正

错误代码(来自用户提问):

java

复制代码
class Solution {
    ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        List<Integer> list = new ArrayList<>();
        for(ListNode pre = list1; pre != null; pre = pre.next) list.add(pre.val);
        for(ListNode pre = list2; pre != null; pre = pre.next) list.add(pre.val);
        ArrayList<Integer> listsort = new ArrayList<>(list);
        Collections.sort(listsort);   // 对 listsort 排序
        ListNode head = new ListNode(0);
        ListNode cur = head;
        for(int i = 0; i < list.size(); i++) {   // ❌ 错误:用了未排序的 list
            cur.next = new ListNode(list.get(i));
            cur = cur.next;
        }
        return head.next;
    }
}

错误原因 :排序的是 listsort,构建链表时却用了原始的 list,导致输出顺序仍为"先list1后list2"。

正确修正(但仍不推荐):

java

复制代码
class Solution {
    ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        List<Integer> list = new ArrayList<>();
        for (ListNode p = list1; p != null; p = p.next) list.add(p.val);
        for (ListNode p = list2; p != null; p = p.next) list.add(p.val);
        Collections.sort(list);   // 直接排序原列表
        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;
        for (int val : list) {
            cur.next = new ListNode(val);
            cur = cur.next;
        }
        return dummy.next;
    }
}

复杂度:时间 O((m+n) log(m+n)),空间 O(m+n)。远不如归并法,面试时请勿使用。

六、解法对比与总结

方法 时间复杂度 空间复杂度 是否利用有序性 面试推荐度
迭代归并 O(m+n) O(1) ⭐⭐⭐⭐⭐
递归归并 O(m+n) O(m+n) ⭐⭐⭐⭐
集合排序 O(n log n) O(n)

6.1 面试建议

  • 首选迭代归并:空间 O(1),最符合面试官期待。

  • 递归可作为备选:代码简洁,但要说明空间代价。

  • 切忌使用排序法:即便能通过,也会被质疑基本功。

6.2 常见扩展问题

  1. 合并 K 个有序链表(LeetCode 23):可使用分治归并或优先队列。

  2. 合并后仍然保持有序:本题已要求,注意处理相等值时的稳定性(通常无影响)。

  3. 不允许创建新节点:必须原地拼接,迭代法天然满足。

七、相关链接

相关推荐
澈2072 小时前
C++并查集:高效解决连通性问题
java·c++·算法
旖-旎3 小时前
深搜练习(单词搜索)(12)
c++·算法·深度优先·力扣
企客宝CRM4 小时前
2026年中小企业CRM选型指南:企客宝CRM处于什么位置?
android·算法·企业微信·rxjava·crm
橙淮4 小时前
二叉树核心概念与Java实现详解
数据结构·算法
米罗篮5 小时前
DSU并查集 & 拓展欧几里得-逆元
c++·经验分享·笔记·算法·青少年编程
橙淮5 小时前
双指针法:高效算法解题的利器
算法
初心未改HD5 小时前
深度学习之MLP与反向传播算法详解
人工智能·深度学习·算法
刀法如飞5 小时前
【Go 字符串查找的 20 种实现方式,用不同思路解决问题】
人工智能·算法·go
技术小黑7 小时前
CNN算法实战系列03 | DenseNet121算法实战与解析
pytorch·深度学习·算法·cnn