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

相关推荐
XLYcmy1 小时前
一个基于 Python 的轻量级 LLM(大语言模型)API 客户端程序:从API交互到LLM应用架构
服务器·python·ai·llm·prompt·agent·token
程序员佳佳1 小时前
四个月长期实测:自建 Milvus、FAISS、原生向量 API 和向量引擎中转方案,到底怎么选?
人工智能·windows·python·gpt·milvus·faiss
shimly1234561 小时前
python3 venv 是啥?
python
aqi001 小时前
15天学会AI应用开发(六)使用离线大模型对文本生成摘要
人工智能·python·ai编程
codecrafter1232 小时前
sh:在 Python 里直接调系统命令
开发语言·python·其他
金銀銅鐵2 小时前
用 Tkinter 实现简单的论语第一章阅读器
后端·python
小玮看世界2 小时前
【技术成长实录】北京地铁12号线数据分析系统:从一个观察到一个完整项目的演进之路
python·人机交互·学习方法·cicd·项目交付
极光代码工作室2 小时前
基于机器学习的金融风险预测系统
python·深度学习·机器学习·ai·系统设计
吴阿福|一人公司2 小时前
Python 类变量修改的压力测试:高并发场景
开发语言·python