# 21. 合并两个有序链表
# 题目描述
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:list1 = [1,2,4], list2 = [1,3,4]
输出:[1,1,2,3,4,4]
2
示例 2:
输入:list1 = [], list2 = []
输出:[]
2
示例 3:
输入:list1 = [], list2 = [0]
输出:[0]
2
提示:
- 两个链表的节点数目范围是
[0, 50] -100 <= Node.val <= 100list1和list2均按非递减顺序排列
# 思路
两个链表本身已经有序,怎么利用这个条件呢?
只需要比较两个链表当前节点的值,谁更小,就把谁接到结果链表后面,然后让对应链表的指针向后移动一位。
例如 list1 当前是 2,list2 当前是 3,那么 2 一定是剩余节点中的最小值,可以放心地把节点 2 接入结果链表。
这里有两个问题需要想清楚。
第一个问题:结果链表的头结点怎么处理?
如果直接操作真正的头结点,每接入一个节点,都要判断结果链表是不是空。为了统一操作,我们定义一个虚拟头结点 dummy,再用 cur 指向结果链表的尾部:
dummy:固定不动,方便最后找到结果链表的头结点cur:始终指向已合并部分的最后一个节点list1、list2:分别指向两个链表中还没有处理的第一个节点
每轮循环只做三件事:
- 比较
list1->val和list2->val - 把较小的节点接到
cur->next - 移动被选中的链表指针,再移动
cur
第二个问题:如果一个链表先遍历完了怎么办?
另一个链表剩余部分本来就是有序的,并且其中所有节点都不小于已经合并的节点,所以不需要继续逐个比较,直接把剩余链表整体接到 cur->next 即可。
最后返回 dummy.next,因为 dummy 只是为了方便操作,并不属于真正的结果链表。
# 模拟过程
以 list1 = [1,2,4]、list2 = [1,3,4] 为例。
先创建虚拟头结点 dummy,令 cur = dummy。此时结果链表为空,list1 和 list2 分别指向两个链表的第一个节点。
比较两个链表的当前节点。相等时我们接入 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;
}
};
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->next 与 list2 合并后的结果”。另一种情况同理。
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;
}
};
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
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;
}
}
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
}
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;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 与代码随想录联系
这道题把代码随想录链表章节里的两个基本功放到了一起:虚拟头结点统一边界处理,指针移动完成链表拼接。
建议录友们把本题和203. 移除链表元素、206. 反转链表放在一起练习。前者帮助理解虚拟头结点,后者帮助熟悉保存节点和改变 next 指向。
评论
验证登录状态...