LeetCode 补充题 185. 逐层循环右移二叉树
题目描述
牛客原题: ✅ 补充题 185. 逐层循环右移二叉树
将二叉树各层的孩子槽位向右循环移动
k位,空槽位也参与。一层的槽位依次是该层每个父节点的左、右孩子。操作从最底层向上进行,移动节点时带着它当前的整棵子树。根节点不移动。
示例 1:
输入:
root = [1,2,3,4], k = 2
输出:[1,2,3,null,null,4]
解释: 用层序序列表示树,null表示空槽。底层槽位[4,null,null,null]右移两位后为[null,null,4,null],所以4成为3的左孩子。根的两个孩子不变。
提示:
-
k为非负整数。 - 空孩子槽位也参与循环移动。
- 按层从下往上处理,节点携带当前子树移动,根节点位置不变。
题意分析
移动对象是孩子指针槽位,空槽也占一个位置,不能只对非空节点值旋转。节点移动时携带整棵子树,所以需要先确定原始各层节点,再按题目要求从底向上调整。
解法:保留空槽并从底层向上旋转
核心思路
[!blue]
先层序保存各层的非空节点引用,固定每一轮的父节点列表。对包含
m个父节点的一层,将它们的左、右孩子依次复制到长度为2m的槽位数组,空指针也照常写入。右移量先取
shift = k % (2m),旧槽位i写到(i+shift) % (2m)。目标下标除以 2 得父节点编号,奇偶决定左或右孩子。先复制全部旧槽再回写,避免前面的改写覆盖后面尚未读取的指针。从最深父节点层开始,子树内部先完成调整,再随上层节点整体移动;原始父节点列表不会被新的层次关系干扰。根位置不变,空树直接返回,单节点树没有可处理的孩子层。
解题步骤
- 层序收集每层原有非空节点。
- 从最深的父节点层向上,按父节点顺序复制全部左右孩子槽位,保留 null。
- 把每个旧槽位映射到右移后的新槽位,回写对应父节点的左或右指针。
代码实现
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
}
}
class Solution {
public TreeNode rotateLevels(TreeNode root, int k) {
if (root == null) {
return null;
}
List<List<TreeNode>> levels = new ArrayList<>();
List<TreeNode> level = new ArrayList<>();
level.add(root);
while (!level.isEmpty()) {
levels.add(level);
List<TreeNode> next = new ArrayList<>();
for (TreeNode node : level) {
if (node.left != null) {
next.add(node.left);
}
if (node.right != null) {
next.add(node.right);
}
}
level = next;
}
for (int depth = levels.size() - 2; depth >= 0; depth--) {
List<TreeNode> parents = levels.get(depth);
int width = parents.size() * 2;
TreeNode[] slots = new TreeNode[width];
for (int i = 0; i < parents.size(); i++) {
slots[2 * i] = parents.get(i).left;
slots[2 * i + 1] = parents.get(i).right;
}
int shift = k % width;
for (int i = 0; i < width; i++) {
int dest = (i + shift) % width;
TreeNode parent = parents.get(dest / 2);
if ((dest & 1) == 0) {
parent.left = slots[i];
} else {
parent.right = slots[i];
}
}
}
return root;
}
}
type TreeNode struct {
Val int
Left, Right *TreeNode
}
func rotateLevels(root *TreeNode, k int) *TreeNode {
if root == nil {
return nil
}
levels := [][]*TreeNode{}
level := []*TreeNode{
root,
}
for len(level) > 0 {
levels = append(levels, level)
next := []*TreeNode{}
for _, node := range level {
if node.Left != nil {
next = append(next, node.Left)
}
if node.Right != nil {
next = append(next, node.Right)
}
}
level = next
}
for depth := len(levels) - 2; depth >= 0; depth-- {
parents := levels[depth]
width := len(parents) * 2
slots := make([]*TreeNode, width)
for i, node := range parents {
slots[2*i], slots[2*i+1] = node.Left, node.Right
}
shift := k % width
for i, node := range slots {
dest := (i + shift) % width
parent := parents[dest/2]
if dest%2 == 0 {
parent.Left = node
} else {
parent.Right = node
}
}
}
return root
}
复杂度分析
- 时间复杂度:$O(n)$。
- 空间复杂度:额外空间 $O(n)$。
关键点总结
[!green]
先复制后回写避免覆盖尚未搬移的指针;从底层处理使节点携带已调整的子树移动,上一层父节点集合仍是原始集合。
易错点总结
[!yellow]
k为非负整数;空孩子槽位也移动;必须自底向上,不能只旋转每层节点值。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 102. 二叉树的层序遍历 | 中等 | 先通过层序遍历保存各层原节点,本题随后重连孩子指针而非只输出节点值。 |
| 189. 轮转数组 | 中等 | 同样把位置 i 映射到 (i+k)%长度,本题旋转的是含空值的孩子槽位。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!