题目描述

✅ 21. 合并两个有序链表

image-20260928183452802

image-20260928183452803

题意分析

给定两条按非递减顺序排列的链表,把它们的节点合并成一条仍然从小到大排列的链表,返回合并后的头节点。

合并需要保留所有节点,值相同的节点也不能丢弃。通过调整原节点的 next 连接完成合并,不必复制每个节点。任意一条链表都可能为空;两条都为空时返回空链表。

解法:哨兵节点迭代合并

核心思路

[!blue]

每条链表都已排序,因此当前头节点就是这条链表剩余部分的最小值。比较两条链表的当前头节点,较小者一定是全部剩余节点中最小的,可以直接接到结果尾部;随后只推进它所属链表的指针,继续比较。值相等时先接任意一个都可以,另一个仍保留在原链表中等待处理。

用哨兵节点 dummy 放在结果头部之前,tail 指向结果的尾节点。这样接入第一个节点和后续节点都只需要设置 tail.next,不用单独判断结果是否为空。接入后让 tail 向后移动,而 dummy 始终不动,通过 dummy.next 保留结果入口。

当一条链表用完时,另一条的剩余节点本身有序,且都不小于已经接入的尾节点,可以直接整段接到 tail.next,无需逐个比较。最后返回 dummy.next,哨兵本身不属于答案。

解题步骤

  1. 创建哨兵节点 dummy,令 tail = dummy。
  2. 两条链表都非空时,比较当前节点的值,将较小节点接到 tail.next。
  3. 将被选中链表的指针移到下一个节点,再让 tail = tail.next,继续比较。
  4. 循环结束后,将未耗尽的链表接到 tail.next,返回 dummy.next。

代码实现

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;

        while (list1 != null && list2 != null) {
            // 复用较小的当前头节点,只推进它所属的链表;哨兵值不参与比较。
            if (list1.val <= list2.val) {
                tail.next = list1;
                list1 = list1.next;
            } else {
                tail.next = list2;
                list2 = list2.next;
            }

            // 接入一个节点后,只向前推进结果尾部。
            tail = tail.next;
        }

        // 剩余后缀本身有序,可以一次接入。
        tail.next = list1 != null ? list1 : list2;

        return dummy.next;
    }
}
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
    dummy := &ListNode{}
    tail := dummy

    for list1 != nil && list2 != nil {
        // 复用较小的当前头节点,只推进它所属的链表;哨兵值不参与比较。
        if list1.Val <= list2.Val {
            tail.Next = list1
            list1 = list1.Next
        } else {
            tail.Next = list2
            list2 = list2.Next
        }
        // 接入一个节点后,只向前推进结果尾部。
        tail = tail.Next
    }

    // 剩余后缀本身有序,可以一次接入。
    if list1 != nil {
        tail.Next = list1
    } else {
        tail.Next = list2
    }
    return dummy.Next
}

复杂度分析

  • 时间复杂度:$O(m + n)$,m、n 为两条链表的长度,每个节点至多参与一次接入操作。
  • 空间复杂度:$O(1)$,复用原节点,只新增一个哨兵节点和少量指针。

关键点总结

[!green]

  • 哨兵节点避免单独处理结果头节点。
  • 循环条件必须是两条链表都非空。
  • 一条链表耗尽后,可直接挂接另一条的剩余部分。

易错点总结

[!yellow]

  • 返回 dummy 或 tail,正确结果头是 dummy.next。
  • 接入节点后忘记移动对应链表指针或 tail。
  • 循环条件写成“或”,导致访问空指针。
  • 忘记接上未耗尽链表的剩余部分。

相似题目

题目 难度 关联与区别
23. 合并 K 个升序链表 困难 两链表归并是合并k条链表的基础子过程,可按分治成对合并。
88. 合并两个有序数组 简单 同样从两个有序来源取下一项,数组版本需避免覆盖尚未读取的数据,常从末尾合并。
148. 排序链表 中等 按有序头节点逐步拼接链表;本题合并两条链表,该题以归并排序反复合并子链表。
补充题 190. 合并两个有序链表并去重 中等 沿用两条链表比较表头、接入较小节点的方法;补充题还需跳过重复值。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/92434499
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!