(LeetCode-Hot100)21. 合并两个有序链表

合并两个有序链表

问题简介

🔗 LeetCode 21. 合并两个有序链表

📝 题目描述

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


🧪 示例说明

示例 1:

复制代码
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

复制代码
输入:l1 = [], l2 = []
输出:[]

示例 3:

复制代码
输入:l1 = [], l2 = [0]
输出:[0]

💡 解题思路

✅ 方法一:递归法(推荐)

核心思想

比较两个链表头节点的值,较小者作为合并后链表的头节点,然后递归处理剩余部分。

步骤如下

  1. 如果 l1 为空,直接返回 l2
  2. 如果 l2 为空,直接返回 l1
  3. l1.val <= l2.val,则 l1.next = mergeTwoLists(l1.next, l2)
  4. 否则,l2.next = mergeTwoLists(l1, l2.next)
  5. 返回较小节点作为当前头节点。

优点:代码简洁,逻辑清晰。

缺点:递归深度可能较大(最坏 O(m+n)),在极端情况下可能导致栈溢出(但 LeetCode 测试用例通常不会触发)。


✅ 方法二:迭代法(双指针)

核心思想

使用一个虚拟头节点(dummy node)简化边界处理,用两个指针分别遍历两个链表,每次选择较小节点连接到结果链表。

步骤如下

  1. 创建虚拟头节点 dummy 和当前指针 cur = dummy
  2. l1l2 都非空时:
    • l1.val <= l2.val,则 cur.next = l1l1 = l1.next
    • 否则 cur.next = l2l2 = l2.next
    • cur = cur.next
  3. 循环结束后,将未遍历完的链表(l1l2)接到 cur.next
  4. 返回 dummy.next

优点:空间复杂度 O(1),无递归开销。

缺点:代码略长,需注意指针操作。


💻 代码实现

java: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;
        }
    }
}

// 方法二:迭代(双指针 + 虚拟头节点)
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(-1);
        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;
    }
}
go:Go 复制代码
// 方法一:递归
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
    if list1 == nil {
        return list2
    }
    if list2 == nil {
        return list1
    }
    if list1.Val <= list2.Val {
        list1.Next = mergeTwoLists(list1.Next, list2)
        return list1
    } else {
        list2.Next = mergeTwoLists(list1, list2.Next)
        return list2
    }
}

// 方法二:迭代
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
    dummy := &ListNode{Val: -1}
    cur := dummy

    for list1 != nil && list2 != nil {
        if list1.Val <= list2.Val {
            cur.Next = list1
            list1 = list1.Next
        } else {
            cur.Next = list2
            list2 = list2.Next
        }
        cur = cur.Next
    }

    if list1 != nil {
        cur.Next = list1
    } else {
        cur.Next = list2
    }

    return dummy.Next
}

🎯 示例演示(以示例1为例)

初始状态:

复制代码
l1: 1 → 2 → 4
l2: 1 → 3 → 4

迭代过程(方法二):

步骤 cur.next l1 l2 结果链表(dummy→...)
0 - 1→2→4 1→3→4 -1
1 1 (l2) 1→2→4 3→4 -1 → 1
2 1 (l1) 2→4 3→4 -1 → 1 → 1
3 2 4 3→4 ... → 1 → 2
4 3 4 4 ... → 2 → 3
5 4 (l1) nil 4 ... → 3 → 4
6 4 (l2) nil nil ... → 4 → 4

最终返回 dummy.next1→1→2→3→4→4


✅ 答案有效性证明

我们需证明合并后的链表满足:

  1. 包含所有原节点 :两种方法均遍历了 l1l2 的所有节点,且未跳过任何节点;
  2. 保持升序:每一步都选择当前最小节点,由数学归纳法可知整体有序;
  3. 正确终止:当任一链表为空时,直接拼接另一链表(其本身有序),保证完整性。

因此,算法正确。


📊 复杂度分析

方法 时间复杂度 空间复杂度 说明
递归 O(m + n) O(m + n) 递归调用栈深度为 m+n
迭代 O(m + n) O(1) 仅使用常数额外空间

其中 mn 分别为两个链表的长度。


📌 问题总结

  • 关键技巧
    • 递归:将大问题分解为"选头 + 合并剩余";
    • 迭代:使用 虚拟头节点(dummy) 避免处理空链表的边界情况。
  • 适用场景
    • 链表合并是归并排序的核心步骤;
    • 在多路归并、K个有序链表合并等问题中会复用此思想。
  • 延伸思考
    • 若扩展为合并 K 个有序链表,可使用优先队列(堆)优化。

💡 建议:面试中优先写迭代解法(空间更优),若时间充裕可补充递归思路展示思维广度。

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
重生之后端学习2 小时前
994. 腐烂的橘子
java·开发语言·数据结构·后端·算法·深度优先
星火开发设计2 小时前
关联式容器:set 与 multiset 的有序存储
java·开发语言·前端·c++·算法
追随者永远是胜利者2 小时前
(LeetCode-Hot100)72. 编辑距离
java·算法·leetcode·职场和发展·go
musenh2 小时前
springmvc学习
java·学习
啊阿狸不会拉杆2 小时前
《计算机视觉:模型、学习和推理》第 2 章-概率概述
人工智能·python·学习·算法·机器学习·计算机视觉·ai
石牌桥网管2 小时前
golang Context介绍
开发语言·算法·golang
Hello.Reader2 小时前
Flink State Backend 选型、配置、RocksDB 调优、ForSt 与 Changelog 一次讲透
java·网络·数据库
_OP_CHEN2 小时前
【算法提高篇】(四)线段树之多个区间操作:懒标记优先级博弈与实战突破
算法·蓝桥杯·线段树·c/c++·区间查询·acm、icpc·区间操作
俩娃妈教编程2 小时前
2025 年 09 月 三级真题(1)--数组清零
c++·算法·gesp真题