# 21. 合并两个有序链表

力扣题目链接 (opens new window)

# 题目描述

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

示例 1:

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

示例 2:

输入:list1 = [], list2 = []
输出:[]
1
2

示例 3:

输入:list1 = [], list2 = [0]
输出:[0]
1
2

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • list1list2 均按非递减顺序排列

# 思路

两个链表本身已经有序,怎么利用这个条件呢?

只需要比较两个链表当前节点的值,谁更小,就把谁接到结果链表后面,然后让对应链表的指针向后移动一位。

例如 list1 当前是 2,list2 当前是 3,那么 2 一定是剩余节点中的最小值,可以放心地把节点 2 接入结果链表。

这里有两个问题需要想清楚。

第一个问题:结果链表的头结点怎么处理?

如果直接操作真正的头结点,每接入一个节点,都要判断结果链表是不是空。为了统一操作,我们定义一个虚拟头结点 dummy,再用 cur 指向结果链表的尾部:

  • dummy:固定不动,方便最后找到结果链表的头结点
  • cur:始终指向已合并部分的最后一个节点
  • list1list2:分别指向两个链表中还没有处理的第一个节点

每轮循环只做三件事:

  1. 比较 list1->vallist2->val
  2. 把较小的节点接到 cur->next
  3. 移动被选中的链表指针,再移动 cur

第二个问题:如果一个链表先遍历完了怎么办?

另一个链表剩余部分本来就是有序的,并且其中所有节点都不小于已经合并的节点,所以不需要继续逐个比较,直接把剩余链表整体接到 cur->next 即可

最后返回 dummy.next,因为 dummy 只是为了方便操作,并不属于真正的结果链表。

# 模拟过程

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

先创建虚拟头结点 dummy,令 cur = dummy。此时结果链表为空,list1list2 分别指向两个链表的第一个节点。

比较两个链表的当前节点。相等时我们接入 list1 的节点 1,随后 list1 指向节点 2,cur 指向刚接入的节点 1。

下一轮比较 2 和 1,接入 list2 的节点 1。后续仍然按照相同规则,依次接入节点 2、3、4。

list1 已经指向空,而 list2 还剩一个节点 4 时,循环结束。直接执行 cur->next = list2,结果链表就是 [1,1,2,3,4,4]

很多录友写这道题时,容易忘记最后接上剩余链表。想清楚循环条件是“两个链表都不为空”,自然就知道循环结束后至少有一个链表为空,还要处理另一个链表。

# 解题代码

# 迭代法

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode dummy(0); // 虚拟头结点,统一头结点的处理
        ListNode* cur = &dummy;

        while (list1 != nullptr && list2 != nullptr) {
            if (list1->val <= list2->val) {
                cur->next = list1;
                list1 = list1->next;
            } else {
                cur->next = list2;
                list2 = list2->next;
            }
            cur = cur->next;
        }

        // 一个链表为空后,直接接上另一个链表的剩余部分
        cur->next = list1 != nullptr ? list1 : list2;
        return dummy.next;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 递归法

递归写法要先明确:当前应该返回哪个节点作为合并后链表的头结点?

如果 list1->val <= list2->val,那么当前头结点一定是 list1。接下来只需要把 list1->next 指向“list1->nextlist2 合并后的结果”。另一种情况同理。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (list1 == nullptr) return list2;
        if (list2 == nullptr) return list1;

        if (list1->val <= list2->val) {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        }

        list2->next = mergeTwoLists(list1, list2->next);
        return list2;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 复杂度分析

迭代法:时间复杂度 O(m + n),空间复杂度 O(1)。

递归法:时间复杂度 O(m + n),空间复杂度 O(m + n),空间消耗来自递归调用栈。

# 其他语言

# Python3

class Solution:
    def mergeTwoLists(self, list1, list2):
        dummy = ListNode(0)
        cur = dummy

        while list1 and list2:
            if list1.val <= list2.val:
                cur.next = list1
                list1 = list1.next
            else:
                cur.next = list2
                list2 = list2.next
            cur = cur.next

        cur.next = list1 if list1 else list2
        return dummy.next
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Go

func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
    dummy := &ListNode{}
    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# JS

var mergeTwoLists = function(list1, list2) {
    const dummy = new ListNode(0);
    let 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;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 与代码随想录联系

这道题把代码随想录链表章节里的两个基本功放到了一起:虚拟头结点统一边界处理,指针移动完成链表拼接

建议录友们把本题和203. 移除链表元素206. 反转链表放在一起练习。前者帮助理解虚拟头结点,后者帮助熟悉保存节点和改变 next 指向。

评论

验证登录状态...