21. 合并两个有序链表
✨核心逻辑
本题采用 双指针(迭代) 的策略:
- 虚拟头节点(Dummy Node):为了方便处理结果链表为空的情况,以及简化头部节点的拼接操作,我们创建一个虚拟头节点
dummy。 - 双指针穿针引线:使用一个工作指针
curr始终指向结果链表的最后一个节点。当两个链表都非空时,比较它们当前节点的值。 - 节点拼接:
- 如果
list1.val小于list2.val,就把list1当前节点接到curr.next后面,并将list1指针后移。 - 反之,则把
list2当前节点接过去,并将list2指针后移。 - 每次拼接后,
curr指针也要同步后移,指向新链表的尾部。
- 如果
- 收尾处理:当
while循环结束时,必定有一条链表已经遍历完。此时,直接将另一条尚未遍历完的链表剩余部分,整体拼接到curr.next后面即可。
🔥代码实现(含详细变量注释)
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// dummy:虚拟头节点(哑节点),不存储实际数值,它的 next 指向结果链表的真实头节点
ListNode dummy = new ListNode(-1);
// curr:工作指针,始终指向当前合并后新链表的最后一个节点,初始指向 dummy
ListNode curr = dummy;
// 当两个链表都还有节点未遍历完时,进行循环比较
while (list1 != null && list2 != null) {
// 比较两个链表当前节点的值,将较小值的节点拼接到结果链表末尾
if (list1.val < list2.val) {
curr.next = list1; // 将 list1 的当前节点接入结果链表
curr = curr.next; // curr 指针向后移动
list1 = list1.next; // list1 指针向后移动
} else {
curr.next = list2; // 将 list2 的当前节点接入结果链表
curr = curr.next; // curr 指针向后移动
list2 = list2.next; // list2 指针向后移动
}
}
// 循环结束后,如果 list1 还没走完(说明 list2 已经空了),
// 直接将剩余的 list1 整体拼接在 curr 后面
if (list1 != null) {
curr.next = list1;
}
// 如果 list2 还没走完(说明 list1 已经空了),
// 直接将剩余的 list2 整体拼接在 curr 后面
if (list2 != null) {
curr.next = list2;
}
// 返回虚拟头节点的下一个节点,即合并后链表的真正头节点
return dummy.next;
}
}
- ⏱️复杂度分析
时间复杂度:O(M + N),其中 M 和 N 分别是两个链表的长度。我们只需遍历两个链表一次,每一轮迭代都处理其中一个节点,总循环次数为 M + N。
空间复杂度:O(1),我们仅仅使用了 dummy 和 curr 两个额外的指针变量,没有创建新的链表节点,完全是原地拼接,空间开销是常数级的。
🔍总结一下:
原始思路是让一个链表和另一个进行比较,然后插入,但有很多局限性:

我觉得这个思路有一点让我感到很新颖:就是新建一个头节点不断地往这个头节点上拼接比较后的节点。最终形成一个链表,返回其next即可