leetcode
leetcode 1501 ~ 1550
纠正二叉树

纠正二叉树

难度:

标签:

题目描述

代码结果

运行时间: 87 ms, 内存: 21.4 MB


/*
 题目思路: 
 由于Java Stream API不适合直接操作和修改数据结构,因此我们在这里仍然使用经典的遍历方法。
 使用Stream API的部分是为了展示我们如何使用它来进行一些集合操作,例如过滤和查找。
 我们通过遍历树,记录看到的节点,并处理发现的误入节点。
*/
public class Solution {
    public TreeNode correctBinaryTree(TreeNode root) {
        Set<TreeNode> seen = new HashSet<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            Stream.of(node.right, node.left).filter(Objects::nonNull).forEach(child -> {
                if (seen.contains(child)) {
                    return null;
                }
                queue.add(child);
            });
            seen.add(node);
        }
        return root;
    }
}

/* 
假设树节点类如下:
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}
*/

解释

方法:

该题解采用了深度优先搜索(DFS)的方法来解决问题。首先,定义了一个集合 `visited_nodes` 用以记录访问过的节点。在递归函数 `dfs` 中,如果当前节点为空或者当前节点的右子节点已经在 `visited_nodes` 中(即被访问过),则说明存在非法引用,此节点应被删除,返回 `None`。否则,将当前节点添加到 `visited_nodes`,递归处理右子节点和左子节点。通过这种方式,算法从根节点开始,逐层向下检查并剪枝,从而修复二叉树。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在题解中使用 `visited_nodes` 集合来记录节点,为什么选择集合而不是列表或其他数据结构?
在算法中使用集合(`set`)而不是列表(`list`)来记录节点,是因为集合提供了更高效的查找性能。集合基于哈希表实现,其平均时间复杂度为O(1)来检查元素是否存在,而列表的查找操作则需要O(n)的时间复杂度,其中n是列表的长度。因此,在需要频繁检查元素是否存在的场景中,使用集合可以显著提高算法的效率。
🦆
题解中递归删除节点的逻辑是如何确保不会错误删除未被非法引用的有效节点的?
题解通过检查一个节点的右子节点是否已经存在于`visited_nodes`集合中来判断该节点是否应被删除。这种方法确保只有那些其右子节点已经被访问过、即出现非法引用(循环引用)的节点会被删除。该逻辑保证了只有确实存在问题的节点才会被处理,而所有正常的节点都将保持不变。递归过程中对每个节点的这种检查确保了算法的正确性,防止了错误删除有效节点。
🦆
在处理二叉树的右子节点之前就将当前节点添加到 `visited_nodes`,这种顺序对算法的正确性和效率有何影响?
在递归处理节点之前将其添加到`visited_nodes`集合中是必要的,因为这样可以在访问右子节点之前标记当前节点已被访问。这种顺序是为了防止当前节点的右子节点非法地引用到当前节点或其任何祖先节点,这是解题要求的关键部分。在递归进入更深层次的节点之前标记已访问的节点,有助于维护算法的正确性,并且对效率没有负面影响,因为集合的添加操作是O(1)的时间复杂度。
🦆
题解提到当节点的右子节点已经在 `visited_nodes` 中时,就删除该节点。如何处理这种情况下的左子节点,它们是否也需要特别处理?
根据题解的逻辑,当一个节点因为其右子节点已经存在于`visited_nodes`中而被删除时,这个节点的左子节点也会被递归地处理。这是因为在`dfs`函数中,如果当前节点需要删除(即返回`None`),那么其左子节点也会通过递归调用`dfs(node.left)`来相应地处理。这确保了所有可能受影响的子节点都会被适当地检查和处理,无论是左子节点还是右子节点。

相关问题