LeetCode 21 合并两个有序链表:彻底理解虚拟头节点(Dummy)套路
前言
刚开始刷链表题时,经常会遇到一个问题:
新链表的头节点到底该怎么处理?
比如今天这道经典题:
LeetCode 21:合并两个有序链表
很多同学第一次写的时候,代码里会出现大量判断:
python
if head is None:
head = node
else:
...
不仅逻辑复杂,而且容易出错。
而这道题最经典的解法,就是使用链表中的万能技巧:
虚拟头节点(Dummy Node)
学会这个技巧之后,后面大量链表题都能套用同样的思路。
题目描述
给你两个升序链表:
text
list1 = 1 -> 2 -> 4
list2 = 1 -> 3 -> 4
要求将它们合并成一个新的升序链表:
text
1 -> 1 -> 2 -> 3 -> 4 -> 4
并返回合并后的头节点。
为什么需要虚拟头节点?
假设不用 Dummy。
当我们第一次往新链表插入节点时:
python
head = None
就必须额外判断:
python
if head is None:
head = node
后面每次添加节点又是另一套逻辑。
这样会导致:
- 头节点需要特殊处理
- 代码分支增多
- 容易遗漏边界情况
于是就有了 Dummy 技巧。
Dummy 节点是什么?
本质上就是人为创建一个临时节点:
python
dummy = ListNode()
例如:
text
dummy -> None
以后所有节点都统一接在 dummy 后面:
text
dummy -> 1 -> 2 -> 3
最终返回:
python
dummy.next
即可获得真正的链表头节点。
这样就不用再考虑:
text
谁是第一个节点
的问题了。
迭代法
核心思路
同时遍历两个链表:
- 比较当前节点值
- 选择较小节点拼接到新链表
- 对应链表后移
- cur 指针后移
- 其中一个链表结束后,直接拼接剩余部分
代码实现
python
from typing import Optional
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def mergeTwoLists(
self,
list1: Optional[ListNode],
list2: Optional[ListNode]
) -> Optional[ListNode]:
# 创建虚拟头节点
dummy = ListNode()
# 当前拼接位置
cur = dummy
# 同时遍历两个链表
while list1 and 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 if list1 else list2
return dummy.next
代码执行过程
假设:
text
list1 = 1 -> 2 -> 4
list2 = 1 -> 3 -> 4
初始状态:
text
dummy
cur -> dummy
list1 -> 1
list2 -> 1
第一次比较
text
1 >= 1
选择:
text
list2
结果:
text
dummy -> 1
第二次比较
text
1 < 3
选择:
text
list1
结果:
text
dummy -> 1 -> 1
第三次比较
text
2 < 3
结果:
text
dummy -> 1 -> 1 -> 2
第四次比较
text
4 > 3
结果:
text
dummy -> 1 -> 1 -> 2 -> 3
第五次比较
text
4 == 4
选择 list2:
text
dummy -> 1 -> 1 -> 2 -> 3 -> 4
此时:
text
list2 = None
循环结束。
拼接剩余节点
执行:
python
cur.next = list1
得到:
text
dummy
↓
1 -> 1 -> 2 -> 3 -> 4 -> 4
最终返回:
python
dummy.next
即可。
边界情况分析
情况一:一个链表为空
python
list1 = None
list2 = [2,3]
循环不会进入。
直接执行:
python
cur.next = list2
结果正确。
情况二:两个链表都为空
python
list1 = None
list2 = None
返回:
python
None
结果正确。
情况三:长度差异很大
python
list1 = [1,5,6]
list2 = [2]
当:
python
list2
遍历结束后。
剩余:
python
5 -> 6
会整体拼接到末尾。
无需继续比较。
时间复杂度分析
假设:
text
list1长度 = m
list2长度 = n
每个节点最多访问一次。
因此:
text
时间复杂度:O(m+n)
只使用了:
text
dummy
cur
两个额外指针。
因此:
text
空间复杂度:O(1)
Dummy 节点套路总结
这道题真正重要的并不是合并链表。
而是掌握:
虚拟头节点(Dummy)
使用场景:
- 合并链表
- 删除节点
- 分隔链表
- 反转链表
- K个一组翻转链表
核心模板:
python
dummy = ListNode()
cur = dummy
while 条件:
cur.next = 某节点
cur = cur.next
return dummy.next
建议直接背下来。
因为后面大量链表题都会出现。
总结
今天这道题看起来只是简单的链表合并,但背后隐藏着链表题中最重要的一个套路:
Dummy 虚拟头节点。
记住一句话:
能用 Dummy 的地方,尽量用 Dummy。
它最大的价值就是:
- 统一头节点处理逻辑
- 减少特殊判断
- 提高代码可读性
- 降低链表题出错概率
后面刷到链表题时,如果发现自己写了很多:
python
if head is None:
不妨想想:
这里能不能引入一个 Dummy 节点?
很多时候,代码会瞬间清爽很多。
如果你正在刷 LeetCode 链表专题,欢迎在评论区交流你遇到的链表难题,一起把链表彻底拿下!🚀