小木的算法日记-LeetCode之巧用虚拟头节点:轻松解决链表分割问题

dummy=ListNode(-1)代表的就是虚拟节点的意思。

因为dummy本身就有假的虚拟的的意思,而且-1 0 NONE等值(不符合实际情况的)一般也认为是虚拟节点的标志。

分割链表


    def partition(self, head: ListNode, x: int) -> ListNode:
定义一个分割函数,其中head的类型是链表(head既可以是头节点也可以是链表)
        # 存放小于 x 的链表的虚拟头结点
        dummy1 = ListNode(-1) 建一个虚拟头节点1
        # 存放大于等于 x 的链表的虚拟头结点
        dummy2 = ListNode(-1) 建一个虚拟头节点2
        # p1, p2 指针负责生成结果链表
        p1, p2 = dummy1, dummy2       给虚拟节点配上指针
        # p 负责遍历原链表,类似合并两个有序链表的逻辑
        # 这里是将一个链表分解成两个链表
        p = head   给原先的链表也配上指针
        while p:  当p也就是指针所对应的一直有节点时进行这个循环
            if p.val >= x:
                p2.next = p 让p2所对应节点的指针与p链接,然后再让p2往前移动
                p2 = p2.next
            else:
                p1.next = p
                p1 = p1.next   同理
            # 不能直接让 p 指针前进,
            # p = p.next     
            # 断开原链表中的每个节点的 next 指针
            temp = p.next  先将指针对应的下一个节点存放再temp以免丢失
 这里进行分解时要将节点与原来的链表的关系断干净,所以不能直接将指针直接移动到下一个位置,得利用其先进行干净。
            p.next = None 然后让指针对应的节点与之后节点的关系断干净。
            p = temp   之后继续 p=p.next 往前面移动
        # 连接两个链表
        p1.next = dummy2.next  进行链接(因为再循环之外所以代表的就是于其之后的所有节点)

        return dummy1.next 然后返回分割后又链接后的链表

AI总结

巧用虚拟头节点:轻松解决链表分割问题

一、核心问题:链表分割的本质需求

题目定义

给定一个链表的头节点 head 和一个特定值 x,要求将链表分隔为两部分:

  • 所有小于 x的节点排在前面,
  • 所有大于或等于 x的节点排在后面,
  • 并且保留各节点的初始相对顺序

示例解析

输入head = [1, 4, 3, 2, 5, 2]x = 3
输出[1, 2, 2, 4, 3, 5]

  • 小于 3 的节点:1, 2, 2(顺序不变),
  • 大于等于 3 的节点:4, 3, 5(顺序不变),
  • 最终合并为:[小节点部分] + [大节点部分]

二、核心思路:"拆分 - 合并" 的链表手术

直接在原链表上移动节点就像在复杂电路中改线,极易出错。更聪明的做法是:
化整为零,再合二为一—— 先将原链表拆成两个子链表,再按顺序合并。

  1. 创建两个子链表

    • small链表:存储所有小于 x 的节点,
    • large链表:存储所有大于等于 x 的节点。
  2. 遍历拆分
    从头到尾扫描原链表,根据节点值判断归属,分别追加到对应子链表。

  3. 合并结果
    small链表的尾部与large链表的头部连接,形成最终链表。

三、神兵利器:虚拟头节点的妙用

为什么需要虚拟头节点?

想象你要组装玩具火车:

  • 没有虚拟头节点时,添加第一个车厢需要单独处理(比如 "如果火车头为空,就把车厢当车头"),
  • 有了虚拟头节点(类似火车头的挂钩),不管有没有车厢,都可以统一用 "挂钩后面接车厢" 的逻辑。

虚拟头节点的核心价值

  • 简化边界处理:避免判断链表是否为空,
  • 统一操作逻辑:无论链表是否为空,追加节点的代码逻辑一致,
  • 代码更健壮:减少因边界条件漏写导致的 bug。

四、代码实战:从思路到实现

python

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def partition(self, head: ListNode, x: int) -> ListNode:
        # 1. 初始化:创建虚拟头节点和操作指针
        dummy1 = ListNode(-1)  # small链表的虚拟头节点(哨兵)
        p1 = dummy1           # p1指向small链表的尾部,负责追加节点
        
        dummy2 = ListNode(-1)  # large链表的虚拟头节点
        p2 = dummy2           # p2指向large链表的尾部
        
        p = head              # p遍历原链表
        
        # 2. 遍历原链表,拆分节点到两个子链表
        while p:
            if p.val < x:
                # 接到small链表尾部
                p1.next = p
                p1 = p1.next
            else:
                # 接到large链表尾部
                p2.next = p
                p2 = p2.next
            
            # 关键步骤:断开原链表连接,防止链表环
            temp = p.next      # 暂存下一个节点
            p.next = None      # 切断当前节点与原链表的联系
            p = temp           # 移动到下一个节点
        
        # 3. 合并两个子链表:small链表 + large链表
        p1.next = dummy2.next  # small尾部连接large头部
        
        # 4. 返回结果:跳过虚拟头节点,返回真实头节点
        return dummy1.next

五、关键细节解析(重要性评级)

1. 虚拟头节点(★★★★★)

  • 本质:不存储有效数据的 "哨兵节点",作为链表操作的固定锚点。
  • 典型场景
    • 构建新链表时,
    • 处理头节点可能为空的情况,
    • 统一头节点和普通节点的操作逻辑。

2. 双指针技巧(★★★★)

  • p1/p2 指针:分别维护smalllarge链表的尾部,实现 O (1) 时间复杂度的尾部追加。
  • p 指针:遍历原链表,类似 "数据分拣员",决定节点归属。

3. 断开原链表(★★★★)

  • 必须执行p.next = None
    若不切断原链表连接,节点会同时属于原链表和新链表,导致:
    • 链表环(节点的 next 仍指向原链表后续节点),
    • 数据混乱(重复节点或顺序错误)。
  • 类比:搬家时必须切断旧房子的水电,才能真正入住新房。

六、学习成果检验

问题 1:为什么用dummy2.next连接链表?

解析

  • p2指针在循环中不断后移,最终指向large链表的尾部(最后一个节点),
  • dummy2是虚拟头节点,dummy2.next始终指向large链表的真实头节点,
  • 因此p1.next = dummy2.next能正确连接large链表的整个序列,而非仅尾部节点。

问题 2:省略p.next = None会发生什么?

反例
输入head = [1,4,2]x=3,若不切断连接:

  • small链表:1→2,
  • large链表:4,
  • 2.next仍指向原链表的None4.next仍指向原链表的2
  • 合并后链表为1→2→4,但4.next→2形成环,遍历时会陷入死循环。

问题 3:虚拟头节点的核心价值是什么?

答案
解决链表操作中的边界条件问题,避免处理 "空链表" 时的特殊逻辑,让代码对 "头节点" 和 "普通节点" 的操作逻辑统一,提升代码的简洁性和健壮性。

七、总结:从一道题到一类问题

通过本题,我们掌握了两个关键能力:

  1. 链表操作的核心思维:拆分复杂问题为 "遍历 - 处理 - 合并" 的标准流程,
  2. 虚拟头节点的万能技巧:这把 "瑞士军刀" 能解决 90% 的链表边界问题(如合并、删除、插入)。

记住:链表操作的本质是指针的舞蹈,而虚拟头节点就是这场舞蹈中最稳定的支点。多练习、多思考,你会发现更多链表问题的解法都藏在这个核心技巧中!

如果觉得有帮助,欢迎点赞收藏~ 你还想了解哪些算法技巧?评论区告诉我!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值