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

输入: l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
示例 2:
输入: l1 = [], l2 = [] 输出:[]
示例 3:
输入: l1 = [], l2 = [0] 输出:[0]
2. 题解
2.1. 递归
2.1.1. 核心思想
- 递归终止条件(基线条件) 如果其中一个链表是空,直接返回另一个链表。
子问题最小情况:没有节点需要合并,直接返回剩下的链表。
if(list1 == nullptr) return list2;
if(list2 == nullptr) return list1;
- 问题分解 比较两个链表头结点:选值更小的节点作为当前结果的头。 假设
list1->val < list2->val,那么list1就是当前这一步的头节点。 但是list1的next应该是什么?list1->next等于:合并list1->next和list2这两个子链表得到的结果。
list1->next = mergeTwoLists(list1->next, list2);
return list1;
- 原问题:合并
list1, list2 - 子问题:合并
list1->next, list2把子问题的返回结果,接在当前节点后面,然后返回当前节点。
- 归并(回溯) 递归一层一层往下拆,直到触发终止条件; 然后逐层向上返回已经合并好的链表,上层拿到下层合并完成的链表,接到自己的 next。
2.1.2. 代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
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;
} else {
list2->next = mergeTwoLists(list1, list2->next);
return list2;
}
}
};
2.1.3. 复杂度
时间复杂度:,m、n 分别为两条链表长度,每个节点只会被处理一次。 空间复杂度:,消耗来自递归调用栈;链表节点数量很大时,存在栈溢出风险。
2.2. 迭代
2.2.1. 核心思想
核心:手动维护新链表的头指针 head、尾指针 tail,循环从两条有序链表挑选更小节点接到尾部,最后拼接剩余链表。 没有虚拟哑节点,需要专门处理第一个节点的特殊情况。
2.2.2. 代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(list1 == nullptr) return list2;
if(list2 == nullptr) return list1;
ListNode* head = nullptr, * tail = nullptr;
ListNode* p1 = list1, * p2 = list2;
while(p1 && p2) {
if(p1->val < p2->val) {
if(head == nullptr) {
head = tail = p1;
} else {
tail->next = p1;
tail = p1;
}
p1 = p1->next;
} else {
if(head == nullptr) {
head = tail = p2;
} else {
tail->next = p2;
tail = p2;
}
p2 = p2->next;
}
}
if(p1) tail->next = p1;
if(p2) tail->next = p2;
return head;
}
};
带虚拟头节点代码
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode dummy;
ListNode* cur = &dummy;
while(list1 && 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 ? list1 : list2;
return dummy.next;
}
2.2.3. 复杂度
时间复杂度:,m、n 分别为两条链表长度,每个节点只会被处理一次。 空间复杂度:,只使用指针,不新建节点,原地修改链表指针。
2.3. 两种算法对比
| 实现方式 | 时间 | 空间 | 核心特点 | 优缺点 |
|---|---|---|---|---|
| 迭代‑无哨兵 (head+tail) | O(m+n) | O(1) | 手动维护 head、tail,特殊处理首节点,需前置判空 | 代码冗余,容易空指针;不推荐面试写 |
| 迭代‑dummy 虚拟头结点 | O(m+n) | O(1) | 哨兵抹平头节点边界,逻辑统一 | 代码简洁,边界友好,首选 |
| 递归 | O(m+n) | O(m+n) | 分解子问题,回溯拼接 | 代码短;长链表会栈溢出 |

