LeetCode 21 合并两个有序链表:彻底理解虚拟头节点(Dummy)套路

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 复制代码
谁是第一个节点

的问题了。


迭代法

核心思路

同时遍历两个链表:

  1. 比较当前节点值
  2. 选择较小节点拼接到新链表
  3. 对应链表后移
  4. cur 指针后移
  5. 其中一个链表结束后,直接拼接剩余部分

代码实现

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 链表专题,欢迎在评论区交流你遇到的链表难题,一起把链表彻底拿下!🚀

相关推荐
用户8356290780512 小时前
Python 实现 PDF 文件加密与解密方法
后端·python
用户8356290780513 小时前
使用 Python 冻结与拆分 Excel 窗格教程
后端·python
你好潘先生11 小时前
别再记命令了,用 yeero do 说句人话就能跑脚本,而且不烧 token
服务器·python·命令行
Agent_大师11 小时前
WebSocket 行情重连成功,K线缺口不会自动消失
python
荣码11 小时前
LLM结构化输出:让AI返回JSON而不是废话,我踩了4个坑
java·python
copyer_xyf11 小时前
FastAPI 如何连接 MySQL
后端·python
apocelipes1 天前
常用编程语言和库的正则表达式性能对比
c语言·c++·python·性能优化·golang·开发工具和环境
用户8356290780511 天前
使用 Python 在 PDF 中创建与管理书签
后端·python
MeixianAgent1 天前
Python 回测数据入口怎么验?历史 K 线入库前先做 5 个检查
后端·python
咕白m6251 天前
用 Python 实现一键批量查找与替换 Excel 数据
后端·python