【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. 不允许创建新节点:必须原地拼接,迭代法天然满足。

七、相关链接

相关推荐
yu85939582 小时前
利用MATLAB进行木材图像去噪
开发语言·算法·matlab
cpp_25012 小时前
P2722 [USACO3.1] 总分 Score Inflation
数据结构·c++·算法·动态规划·题解·洛谷·背包dp
民乐团扒谱机2 小时前
【源码剖析】MATLAB混响函数底层逻辑拆解:Dattorro算法从公式到音频帧的完整推导
算法
淡海水2 小时前
【AI模型】概念-Token
人工智能·算法
凯瑟琳.奥古斯特2 小时前
数据结构核心知识点精要
数据结构·算法·排序算法
隔壁大炮2 小时前
Day02-04.张量点乘和矩阵乘法
人工智能·pytorch·深度学习·线性代数·算法·矩阵
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【删数问题】:删数问题
c++·算法·贪心·csp·信奥赛
geneculture2 小时前
本真信息观:基于序位守恒的融智学理论框架——人类认知第二次大飞跃的基础
人工智能·算法·机器学习·数据挖掘·融智学的重要应用·哲学与科学统一性·融智时代(杂志)
kronos.荒2 小时前
动态规划——最长递增子序列系列问题(python)
算法·动态规划·最长递增子序列系列问题