合并两个有序链表
957 字
5 分钟
合并两个有序链表
题目链接:21. 合并两个有序链表 - 力扣(LeetCode)
- 难度:简单
- 标签:链表、递归、迭代
题目描述
Note
原题说明: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例图解
根据题意,合并过程如下所示(使用 Mermaid 流程图表示):
graph LR
subgraph List1 [链表 1]
L1_1((1)) --红色--> L1_2((2)) --> L1_4((4))
end
subgraph List2 [链表 2]
L2_1((1)) --紫色--> L2_3((3)) --> L2_4((4))
end
subgraph Merged [合并结果]
M1((1)) --> M2((1)) --> M3((2)) --> M4((3)) --> M5((4)) --> M6((4))
style M1 fill:#8a2be2,color:#fff
style M2 fill:#ff0000,color:#fff
style M3 fill:#ff0000,color:#fff
style M4 fill:#8a2be2,color:#fff
style M5 fill:#ff0000,color:#fff
style M6 fill:#8a2be2,color:#fff
end
方案一:暴力解法(先合并,再排序)
核心思路:
将两个链表的所有节点先强行串在一起,然后把所有 val 提取出来存入数组进行排序,最后按顺序写回各节点。
源码实现
class Solution {public: ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { // 1. 在栈上创建哨兵节点,方便统一操作 ListNode dummy(0); ListNode *tail = &dummy;
// 2. 先把两个链表简单粗暴地连起来 for(ListNode *p = list1; p; p = p->next) tail = tail -> next = p; for(ListNode *p = list2; p; p = p->next) tail = tail -> next = p;
// 3. 提取所有值并排序 std::vector<int> vals; for(ListNode *p = dummy.next; p; p = p->next) vals.push_back(p->val); std::sort(vals.begin(), vals.end());
// 4. 将排好序的值写回原节点 ListNode *cur = dummy.next; for(int v : vals){ cur -> val = v; cur = cur -> next; } return dummy.next; }};复杂度分析
- 时间复杂度:。 为两个链表的节点总数。排序的开销占据了主导。
- 空间复杂度:。由于使用了
vector来存储所有节点的值,因此需要额外的线性空间。
方案二:迭代归并法(标准解法)
核心思路: 利用原链表本身就是升序的特点,准备一个新链表的头。每次比较两个旧链表当前的头结点,将较小的那个接在新链表尾部。
源码实现
class Solution {public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { ListNode dummy(0); // 哨兵头节点 ListNode* tail = &dummy; // 指向新链表的末尾
// 归并核心:双指针比较 while (l1 && l2) { if (l1->val < l2->val) { tail->next = l1; l1 = l1->next; } else { tail->next = l2; l2 = l2->next; } tail = tail->next; }
// 拼接剩余段(当一条链走完,另一条链剩下的直接挂在尾部) tail->next = l1 ? l1 : l2;
return dummy.next; }};复杂度分析
- 时间复杂度:。其中 和 是两个链表的长度。我们只需要遍历两个链表的所有节点。
- 空间复杂度:。我们只改变了已有节点的指针指向,没有使用额外的存储结构。
方案三:递归法(极其简洁)
核心思路:
如果其中一个链表为空,直接返回另一个。否则,比较头节点,将较小者的 next 指向“剩余部分的合并结果”。
class Solution {public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { if (!l1) return l2; if (!l2) return l1;
if (l1->val < l2->val) { l1->next = mergeTwoLists(l1->next, l2); return l1; } else { l2->next = mergeTwoLists(l1, l2->next); return l2; } }};复杂度分析
- 时间复杂度:。
- 空间复杂度:。递归调用的深度取决于节点的总数,系统栈空间开销与节点数成正比。
总结
- 理解哨兵节点 (Dummy Node):在处理链表问题时,哨兵节点可以极大地简化边界情况的处理(如空链表、头结点插入等)。
- 空间换时间 vs 原地修改:方案一虽然直观,但效率较低且破坏了原链表。方案二和三是面试的首选。
Tip
以后遇到两个有序序列的合并,“双指针 + 比较” 的归并思想永远是最优解!
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
最后更新于 2025-10-09,距今已过 202 天
部分内容可能已过时
相关文章 智能推荐
1
合并两个有序数组
算法 力扣第88题:合并两个有序数组。从正向合并的繁琐到逆向双指针的极简之美,实现 $O(m+n)$ 的高效原地合并。
2
删除排序链表中的重复元素
算法 力扣第83题:删除排序链表中的重复元素。深入理解链表指针操作,掌握“跳过”重复节点的优雅技巧。
3
对称二叉树
算法 力扣第101题:对称二叉树。深入理解树的镜像对称性质,掌握递归与迭代两种姿势下的双向比对技巧。
4
二叉树的中序遍历
算法 力扣第94题:二叉树的中序遍历。从递归的简洁到迭代的深度剖析,掌握树结构遍历的核心逻辑。
5
相同的树
算法 力扣第100题:相同的树。利用深度优先搜索(DFS)的递归特性,优雅地判断两棵二叉树的结构与数值一致性。
随机文章 随机推荐