1. 项目概述:当链表有了“子节点”
如果你刷过一些链表题,可能会觉得链表无非就是 val 和 next ,顶多再加个 prev 变成双向链表。但 Leetcode 430 这道“扁平化多级双向链表”的题目,直接把复杂度提升了一个维度:它引入了一个叫 child 的指针。这不再是简单的“一条线”,而是一棵可能在任何节点分叉的“树”,只不过这棵树是用链表节点连起来的。
想象一下,你手头有一份文档的大纲,主标题(一级节点)下可能有子标题(二级节点),子标题下还有更细的说明(三级节点)。这份大纲在内存里就是用这种带 child 指针的链表存储的。现在,老板要求你把这份“层级式”的大纲,转换成一份“扁平化”的、所有内容按深度优先顺序依次排列的纯文本列表。Leetcode 430 要解决的就是这个问题:遍历这棵“链表树”,按照深度优先的顺序,将所有的 child 链表“拉平”,并插入到当前节点和它的下一个节点之间,最终形成一个单一、绵长的双向链表。
这题之所以被频繁讨论(从相关热词如“链表遍历”、“链表插入”的高频出现可见一斑),是因为它完美融合了链表的基础操作(遍历、插入、指针修改)和树的深度优先搜索(DFS)思想。它考察的不仅仅是你会不会写 while 循环移动指针,更是考察你能否在复杂的指针关系变化中保持清晰的逻辑,处理好 prev 、 next 和 child 这三个指针的“牵一发而动全身”。很多朋友在初次尝试时,很容易被绕晕,导致链表断裂或者形成环。接下来,我就结合自己多次调试和教学的经验,把这道题的“里子”和“面子”都拆解清楚。
2. 核心思路拆解:递归与迭代的抉择
面对这种具有“层级”或“嵌套”结构的问题,我们的第一反应往往是递归。因为递归的思想天然契合“深度优先”:处理当前节点,如果它有孩子,就一头扎进去处理孩子链表,处理完了再回来继续。这题用递归实现确实非常直观。
2.1 递归解法:清晰但需注意细节
递归函数 dfs(node) 的核心职责是:扁平化以 node 为头节点的链表(及其所有子链表),并返回扁平化后的 尾节点 。返回尾节点是关键,因为父节点需要知道子链表处理完后,应该接回哪里。
递归过程可以分解为以下几步:
- 初始化 :定义
current指针指向当前节点,tail指针用于记录当前链表的最后一个节点,初始化为current。 - 循环处理 :只要
current不为空,就持续处理。 - 保存后继 :这是极易出错的一步!在处理
current的child之前,必须先用一个临时变量next_node保存current.next。因为一旦我们开始处理child,current.next会被修改指向子链表的头节点,原来的后继关系就丢失了。 - 处理子链表 :如果
current.child不为空:- 递归调用
dfs(current.child),得到子链表扁平化后的尾节点child_tail。 - 重新接线 :
- 将
current.next指向current.child。 - 将
current.child.prev指向current。 - 这是将子链表“拉出来”接到主链上的关键操作。
- 将
- 清空 child 指针 :题目要求扁平化后所有
child指针都应置为null。 - 连接子链表尾部 :将
child_tail.next指向我们之前保存的next_node。 - 如果
next_node不为空(即当前节点不是原链表的最后一个节点),需要将next_node.prev指向child_tail。 - 更新 tail :此时,整个链表(包含刚接入的子链表)的尾节点变成了
child_tail(或者如果child_tail后面还有next_node,则 tail 会在后续循环中更新,但这里tail应更新为child_tail以确保递归返回正确的尾节点)。
- 递归调用
- 移动指针 :无论是否有
child,都将current移动到它的next节点(注意,此时的next可能已经是子链表的头节点了),并更新tail为current(当current不为空时)。 - 返回尾节点 :当
current为空,循环结束,返回tail。
注意 :递归解法在逻辑上清晰,但需要警惕栈溢出风险。虽然本题的测试数据通常不会让递归深度达到溢出程度,但在工业级代码或层级极深的情况下,迭代解法是更安全的选择。
2.2 迭代解法:模拟栈的深度优先遍历
迭代解法的核心思想是 显式地使用一个栈(Stack)来模拟递归的调用过程 。我们沿着 next 指针一路向前,当遇到有 child 的节点时,我们并不立即深入,而是把当前节点的“未来”(即 next 节点)先压入栈中保存起来,然后转向处理 child 。等 child 这条分支处理完了,再从栈里把之前保存的“未来”弹出来继续处理。
具体步骤:
- 创建一个栈
stack,初始化当前指针curr = head。 -
while循环,只要curr不为空,或者栈不为空(还有未处理的分支),就继续。 - 如果
curr.child


338

被折叠的 条评论
为什么被折叠?



