原题链接: 21. 合并两个有序链表 - 力扣(Leetcode)
tag: 链表.
一. 题目
将两个升序链表合并为一个新的 升序 链表并返回, 新链表是通过拼接给定的两个链表的所有节点组成的.
二. 题解
本题我们通过将两个链表的节点 尾插 到合并的链表中来合并链表.
由于尾插第一个节点时合并的链表中没有节点, 即 tail == nullptr
, 无法通过 tail->next = l1
或者 tail->next = l2
来尾插节点, 需要分类讨论 (特判 tail
是否为空).
为了避免这种额外的讨论, 我们引入虚拟头节点(dummy
)的概念.
设置虚拟头节点, 即在链表的头节点前新插入一个节点. 如此, 链表的头节点便有了前驱节点(即dummy
), 尾插时就不需要进行额外的讨论.
合并前.
设置一个虚拟头节点 dummy
.
ListNode* dummy = new ListNode();
定义尾指针 tail
, 指向 dummy
.
ListNode* tail = dummy;
开始合并链表.
l1-val == l2->val
取 l2
所指节点尾插.
tail->next = l2;
更新 l2
.
l2 = l2->next;
更新 tail
指针.
tail = tail->next;
继续合并链表.
l1-val < l2->val
取 l1
所指节点尾插.
tail->next = l1;
更新 l1
.
l1 = l1->next;
更新 tail
指针.
tail = tail->next;
继续合并链表.
l1-val < l2->val
取 l1
所指节点尾插.
tail->next = l1;
更新 l1
.
l1 = l1->next;
更新 tail
指针.
tail = tail->next;
l1 == nullptr
, while
循环终止.
l2 != nullptr
将 l2
后的所有节点接到合并的链表上去.
tail->next = l2;
返回合并链表的头节点.
ListNode* head = dummy->next;
delete dummy;
合并后.
三. 复杂度分析
时间复杂度: O(M + N), M 和 N 分别为两个链表的长度. 每次循环迭代中, l1
和 l2
中只有一个节点会被放进合并的链表中, 因此 while
循环的次数不会超过两个链表的长度之和. 除循环外的其他所有操作的时间复杂度都是常数级别的, 因此时间复杂度为 O(M + N).
空间复杂度: O(1).
四. 代码
/**
* 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* l1, ListNode* l2) {
ListNode* dummy = new ListNode(); // 设置一个虚拟头节点
ListNode* tail = dummy; // 定义尾指针 tail
while (l1 && l2) {
if (l1->val < l2->val) {
tail->next = l1; // 将 l1 当前所指节点插入合并链表中
l1 = l1->next; // 更新 l1
} else {
tail->next = l2; // 将 l2 当前所指节点插入合并链表中
l2 = l2->next; // 更新 l2
}
tail = tail->next; // 更新 tail 指针
}
if (l1) tail->next = l1; // 将 l1 中剩余的节点接入合并链表中
if (l2) tail->next = l2; // 将 l2 中剩余的节点接入合并链表中
ListNode* head = dummy->next;
delete dummy;
return head;
}
};