题目描述

✅ 117. 填充每个节点的下一个右侧节点指针 II

image-20260928215006925

image-20260928215006926

题意分析

给普通二叉树的每个节点补上 next 指针,使它指向同一层中紧邻右侧的节点;每层最后一个节点的 next 应为空。树的左右孩子关系保持不变,函数最终返回原来的根节点。

同层相邻节点不一定来自同一个父节点,中间也可能隔着多个没有孩子的父节点。本题不保证二叉树是完美的,左右孩子都可能缺失,不能根据固定的父子组合直接决定跨父节点的连接。

所有 next 初始都为空,空树也是合法输入。题目的进阶要求使用常数额外空间,因此需要在逐层处理时复用已经建立的指针,而不是保存整层节点的队列。

解法:利用已建立的 next 链逐层连接

核心思路

[!blue]

普通层序遍历会用队列记录同层节点。如果当前层的 next 已经从左到右连好,就可以直接沿这条链访问本层,用它替代队列。根节点独占第一层,初始 next 为空,天然是一条已经建立好的层链,能够作为起点。

扫描当前层时,另外构建下一层的链。用 dummy 作为不属于原树的哑节点,用 tail 指向下一层链的最后一个节点。遇到非空左孩子就把它接到尾部,再接非空右孩子;每接入一个孩子,都同步推进 tail。哑节点使追加第一个孩子与追加后续孩子使用完全相同的操作。

正确顺序来自两点:当前层的父节点按从左到右访问,同一个父节点的孩子按左、右顺序加入。因此下一层所有非空节点也恰好按从左到右的顺序连接。缺少孩子的父节点只需跳过,不影响后来遇到的孩子接到已有链尾,跨父节点连接也自然完成。

本轮沿当前层的 next 遍历,只修改下一层孩子的 next,不会破坏当前遍历通道。扫描完后,dummy.next 就是下一层最左节点,把它交给下一轮;下一层已经完整连好,继续满足相同的不变量。

题目保证初始指针为空,下一层最后一个节点不会被接上后继,所以它的 next 保持为空。如果当前层没有任何孩子,dummy.next 也为空,外层循环结束。每轮重新创建哑节点,避免把上一层的链头带入新一层。

解题步骤

  1. 令 levelStart = root,表示当前层最左节点;根为空时直接结束。
  2. 每轮创建 dummy,令 tail = dummy,准备构建下一层。
  3. 从 levelStart 开始沿当前层的 next 链依次访问父节点。
  4. 非空左孩子先追加到 tail 后并更新尾指针,再独立检查右孩子并执行同样操作。
  5. 当前层扫描完成后,令 levelStart = dummy.next,进入刚连接好的下一层。
  6. 新层起点为空时结束,返回原根节点。

代码实现

class Solution {
    public Node connect(Node root) {
        for (Node levelStart = root; levelStart != null; ) {
            // 每层重新记录下一层头尾,当前层只沿已有连接遍历。
            Node dummy = new Node(0);
            Node tail = dummy;

            for (Node node = levelStart; node != null; node = node.next) {
                if (node.left != null) {
                    tail.next = node.left;
                    tail = tail.next;
                }

                if (node.right != null) {
                    tail.next = node.right;
                    tail = tail.next;
                }
            }

            // 当前层处理完,下一轮从刚生成的链头开始。
            levelStart = dummy.next;
        }

        return root;
    }
}
func connect(root *Node) *Node {
    for levelStart := root; levelStart != nil; {
        // 每层重新记录下一层头尾,当前层只沿已有连接遍历。
        dummy := &Node{}
        tail := dummy

        for node := levelStart; node != nil; node = node.Next {
            if node.Left != nil {
                tail.Next = node.Left
                tail = tail.Next
            }
            if node.Right != nil {
                tail.Next = node.Right
                tail = tail.Next
            }
        }
        // 当前层处理完,下一轮从刚生成的链头开始。
        levelStart = dummy.Next
    }
    return root
}

复杂度分析

  • 时间复杂度:O(n)。每个节点在自己所在层被扫描一次,每个孩子最多被接入下一层一次。
  • 空间复杂度:O(1)。任意时刻只需当前层起点、遍历指针、下一层头尾及一个哑节点;每层处理完后旧哑节点不再需要,没有队列或递归栈。

关键点总结

[!green]

  • 当前层完整的 next 链既是上一轮的结果,也是本轮的遍历工具。
  • 遍历父节点与构建孩子链分属两层,写入不会影响当前层的读取。
  • 哑节点与尾指针统一追加操作,不要求相邻父节点拥有固定形态的孩子。
  • 初始 next 为空保证各层尾节点无需额外清空,最终从没有孩子的一层自然结束。

易错点总结

[!yellow]

  • 左右孩子写成 if / else:左右孩子可能同时存在,必须分别判断,否则会漏掉一个。
  • 先连右孩子再连左孩子:会颠倒同一个父节点的孩子顺序,破坏层内从左到右的要求。
  • 追加后不移动 tail:下一次追加会覆盖刚建立的连接,丢失已经接入的节点。
  • 每轮沿用旧的下一层链头:当前层没有孩子时仍可能回到旧链,必须重新初始化哑节点和尾指针。
  • 用 levelStart.next 进入下一层:它仍位于当前层,真正的下一层起点是 dummy.next。
  • 直接连接到右邻父节点的左孩子:右邻父节点可能没有左孩子甚至没有孩子,应该统一按顺序追加所有非空孩子。

相似题目

题目 难度 关联与区别
116. 填充每个节点的下一个右侧节点指针 中等 去掉完美二叉树前提后,不能假设相邻父节点都有孩子,常用下一层哑节点串接。
102. 二叉树的层序遍历 中等 同层相邻关系来自BFS顺序,next链可替代显式队列遍历每层。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/44642653
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!