leetcode
leetcode 1401 ~ 1450
移动 N 叉树的子树

移动 N 叉树的子树

难度:

标签:

题目描述

代码结果

运行时间: 61 ms, 内存: 17.0 MB


// 题目思路:
// 给定一个 N 叉树,和两个节点 `node` 和 `newParent`。
// 要求把 `node` 及其子树移动到 `newParent` 的子节点中。
// 假设输入的树节点具有以下结构:
// class Node {
//     public int val;
//     public List<Node> children;
//     public Node() {}
//     public Node(int _val) {
//         val = _val;
//     }
//     public Node(int _val, List<Node> _children) {
//         val = _val;
//         children = _children;
//     }
// }

import java.util.*;
import java.util.stream.*;

class Solution {
    public Node moveSubTree(Node root, Node node, Node newParent) {
        if (root == null || node == null || newParent == null) return root;

        // 如果 newParent 是 node 或 node 的子节点,直接返回 root
        if (isDescendant(node, newParent)) return root;

        // 移除 node
        removeNode(root, node);

        // 将 node 添加到 newParent 的子节点
        newParent.children = Stream.concat(newParent.children.stream(), Stream.of(node))
                                    .collect(Collectors.toList());

        return root;
    }

    private boolean isDescendant(Node node, Node target) {
        return node == target || node.children.stream().anyMatch(child -> isDescendant(child, target));
    }

    private void removeNode(Node parent, Node node) {
        parent.children = parent.children.stream()
                              .filter(child -> !child.equals(node))
                              .collect(Collectors.toList());
        parent.children.forEach(child -> removeNode(child, node));
    }
}

解释

方法:

此题解的核心思路是通过一次深度优先搜索(DFS)找到节点p和q的父节点以及判断p是否位于q的子树中。首先,创建一个虚拟根节点,其唯一的子节点是真实的根节点,这样可以统一处理边界条件。在DFS过程中,如果遇到节点p,则记录其父节点并标记当前路径下所有节点都在p的子树中。类似地,遇到节点q时记录其父节点。若此时已标记在p的子树中,则表明q在p的子树中。处理移动逻辑时,考虑两种情况:若p直接是q的父节点,则不需要移动;若p在q的子树中,需要先从q的父节点子列表中移除q,再将q作为p的子节点。若p不在q的子树中,则从p的父节点子列表中移除p,并将p作为q的子节点。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在DFS过程中,如何确保只访问每个节点一次,避免重复访问造成的性能问题?
在DFS过程中,确保每个节点只被访问一次的关键在于递归的结构。每当进入一个节点时,它会自动递归地访问其所有子节点。由于树结构的特性,没有回路,因此每个节点只会在其父节点被访问时被访问一次,不需要额外的机制来防止重复访问。这种深度优先搜索确保了每个节点只被访问一次,从而避免了性能损失。
🦆
创建虚拟根节点的策略主要是为了解决哪些边界条件?具体是如何简化问题的?
创建虚拟根节点主要是为了处理根节点也可能是移动操作的目标或源节点的情况,这样可以避免处理根节点的特殊情况。虚拟根节点作为所有节点(包括原始根节点)的父节点,这样在处理节点的父子关系时,即使是根节点也可以像处理其他节点一样,简化了代码逻辑,避免了在处理根节点时需要额外的条件判断。
🦆
在执行`q_fa.children.remove(q)`和`p_fa.children.remove(p)`时,如果子节点列表很大,这些操作的效率如何?是否有更高效的数据结构来处理这种删除操作?
在子节点列表很大时,执行`remove`操作效率较低,因为这需要遍历整个子节点列表来找到要删除的节点,其时间复杂度为O(n)。若要提高效率,可以考虑使用哈希集合(HashSet)来存储子节点,这样删除和查找操作的平均时间复杂度都是O(1)。然而,这需要额外的空间来存储哈希值,并可能需要更复杂的节点管理策略来维护父子关系的一致性。
🦆
代码中如何处理p和q是同一个节点的情况?这种情况下的移动逻辑是什么?
如果p和q是同一个节点,根据题解的逻辑,首先会确定p(即q)的父节点,然后检查是否p在q的子树中,这里会发现p确实在自己的子树中。然后,根据算法逻辑,会尝试将p(作为q)移动到其自身为子节点,这实际是不需要任何操作的。因此,在p和q为同一节点时,本质上不会进行任何移动操作,算法会直接返回原始的根节点。

相关问题