题目描述:
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]输出:[1,1,2,3,4,4]示例 2:
输入:l1 = [], l2 = []输出:[]示例 3:
输入:l1 = [], l2 = [0]输出:[0]
解题思路:
我的思路是:先把两个链表的值按顺序合并到一个 vector,再用 vector 的值新建链表。
具体步骤:
处理边界:如果有一个链表为空,直接返回另一个
双指针合并:用
list1和list2逐个比较,把较小的值存入value处理剩余:把没遍历完的链表剩余部分全部存入
value重建链表:遍历
value,逐个new ListNode,连接成新链表
用例子走一遍:
list1 = 1 -> 2 -> 4,list2 = 1 -> 3 -> 4
| 步骤 | 比较 | 存入 value |
|---|---|---|
| 1 | 1 <= 1 | 1(list1 后移) |
| 2 | 2 > 1 | 1(list2 后移) |
| 3 | 2 <= 3 | 2(list1 后移) |
| 4 | 4 > 3 | 3(list2 后移) |
| 5 | 4 <= 4 | 4(list1 后移) |
| 6 | list1 为空 | 4(list2 后移) |
value = [1, 1, 2, 3, 4, 4]
重建链表:1 -> 1 -> 2 -> 3 -> 4 -> 4✅
代码实现:
class Solution { public: ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { if(list1==nullptr&&list2==nullptr) return list1; if(list1==nullptr) return list2; if(list2==nullptr) return list1; vector<int> value; while(list1!=nullptr&&list2!=nullptr){ if(list1->val<=list2->val){ value.push_back(list1->val); list1=list1->next; } else{ value.push_back(list2->val); list2=list2->next; } } if(list1==nullptr){ while(list2!=nullptr){ value.push_back(list2->val); list2=list2->next; } } else{ while(list1!=nullptr){ value.push_back(list1->val); list1=list1->next; } } ListNode *head = nullptr; ListNode *tail = nullptr; for (int x : value) { ListNode *node = new ListNode(x); if (head == nullptr) { head = node; tail = node; } else { tail->next = node; tail = node; } } return head; } };复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n + m) | 遍历两个链表各一次,重建链表一次 |
| 空间复杂度 | O(n + m) | vector 存储所有值 + 新建链表节点 |
问题:额外使用了 O(n + m) 的空间,看了题解后发现这道题的标准解法可以做到O(1) 空间(原地合并)。
最优解法:原地合并(O(1) 空间)
核心思路:
不需要 vector,直接修改指针,把两个链表的节点按顺序串起来。
用虚拟头结点(dummy node)简化操作:
dummy是虚拟头结点,不存实际值curr指向当前合并后的最后一个节点每次比较
list1和list2,把较小的节点接到curr->next最后把剩余的链表直接接上
具体过程示例:
list1 = 1 -> 2 -> 4,list2 = 1 -> 3 -> 4
dummy -> null curr = dummy 第1步: 1 <= 1, 接 list1 的 1 dummy -> 1 curr = 1, list1 = 2 第2步: 2 > 1, 接 list2 的 1 dummy -> 1 -> 1 curr = 1, list2 = 3 第3步: 2 <= 3, 接 list1 的 2 dummy -> 1 -> 1 -> 2 curr = 2, list1 = 4 第4步: 4 > 3, 接 list2 的 3 dummy -> 1 -> 1 -> 2 -> 3 curr = 3, list2 = 4 第5步: 4 <= 4, 接 list1 的 4 dummy -> 1 -> 1 -> 2 -> 3 -> 4 curr = 4, list1 = null 第6步: list1 为空, 接 list2 的剩余部分 dummy -> 1 -> 1 -> 2 -> 3 -> 4 -> 4 返回 dummy->next ✅
代码实现:
class Solution { public: ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { ListNode *dummy = new ListNode(0); // 虚拟头结点 ListNode *curr = dummy; while (list1 != nullptr && list2 != nullptr) { if (list1->val <= list2->val) { curr->next = list1; list1 = list1->next; } else { curr->next = list2; list2 = list2->next; } curr = curr->next; } // 接上剩余的链表 curr->next = (list1 != nullptr) ? list1 : list2; return dummy->next; } };复杂度分析:
| 维度 | 复杂度 |
|---|---|
| 时间复杂度 | O(n + m),遍历两个链表各一次 |
| 空间复杂度 | O(1),只用了几个指针(不算虚拟头结点) |
两种方法对比:
| 对比维度 | 我的方法(vector重建) | 原地合并 |
|---|---|---|
| 时间复杂度 | O(n + m) | O(n + m) |
| 空间复杂度 | O(n + m) | O(1) |
| 是否修改原链表 | ❌ 不修改 | ✅ 修改 |
| 是否需要新建节点 | ✅ 需要 new n+m 个 | ❌ 不需要 |
| 代码复杂度 | 较长 | 极简 |
| 推荐度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
关键细节:
1. 虚拟头结点的作用
ListNode *dummy = new ListNode(0); ListNode *curr = dummy;dummy不存实际值,只是方便返回结果如果不使用 dummy,需要单独处理"第一个节点"的情况,代码会复杂很多
返回时返回
dummy->next即可
2. 为什么最后直接接剩余部分?
curr->next = (list1 != nullptr) ? list1 : list2;因为list1和list2本身已经是有序的,剩余的部分不需要再逐个比较,直接接上即可。
总结:
| 要点 | 说明 |
|---|---|
| 我的代码 | 时间 O(n+m),空间 O(n+m),正确但空间不是最优 |
| 最优解法 | 虚拟头结点 + 原地合并,时间 O(n+m),空间 O(1) |
| 核心思想 | 逐个比较,把较小的节点接到结果链表 |
| 关键技巧 | 虚拟头结点简化边界处理 |
这道题是链表合并的经典题,掌握后可以解决很多链表题(比如合并 K 个有序链表、排序链表等)。