【树】力扣1110:删点成林

给出二叉树的根节点 root,树上每个节点都有一个不同的值。

如果节点值在 to_delete 中出现,我们就把该节点从树上删去,最后得到一个森林(一些不相交的树构成的集合)。

返回森林中的每棵树。你可以按任意顺序组织答案。

示例:

image
输入:root = [1,2,3,4,5,6,7], to_delete = [3,5]
输出:[[1,2,null,4],[6],[7]]

第一次遇到没有官方题解的题~

输入是一个整数二叉树和一个一维整数数组,输出一个数组,每个位置存储一个子树(的根节点)。

二叉树的题大多可以用递归,从而想到深度优先搜索DFS。

后序遍历

对于被删除节点类型,可以分情况进行分析:

  • 叶子节点:直接删掉

  • 非叶子节点:如果左右子树不为空,就将左右子树加入结果集

为了得到这个结果集,每个结点需要做什么?

  • 每个结点要知道自己是否需要被删掉,如果需要被删掉,那么就根据情况执行上一步的分析

在什么时候做这些事?

  • 如果一个结点需要被删掉,那么应该将它与父节点的关系进行移除,所以需要:判断完结点是否需要被删除后,告知其父节点进行相应的指针操作

那么不妨采用后序遍历的方式,定义一个删除函数:删除以 node 为根结点的树中结点值出现在 to_delete 中的结点,更新结果 ans,并返回删除后新的根结点。

在执行这个删除函数之前,需要做什么准备工作呢?

  • 将最初的根结点加入结果集

    • 这么做的原因是:如果没有要删除的结点,那么删除函数是不会将根结点加入结果集的,所以需要提前加入 root 结点。在删除函数中会维护指针索引,所以结果集中的树结构会保证正确
  • 为了减少搜索 to_delete 的时间,可以将数组转换为 set

class Solution:
    def delNodes(self, root: TreeNode, to_delete: List[int]) -> List[TreeNode]:
        # 两种边界
        if not root:
            return []
        if len(to_delete) == 0:
            return [root]

        ans = [] # 要返回的数组
        delete = set(to_delete)

        # 如果什么都不需要删掉,那么结果集中应该包含原本这棵树。相当于初始化结果集 ans
        if root:
            ans.append(root)

        # 辅函数
        def dfs(node):
            if not node: # base case
                return None
            # 先递归左右子树,定义 node 的左右子结点分别为 node.left 和 node.right
            node.left = dfs(node.left)
            node.right = dfs(node.right)
            # 当 node 结点需要被删掉时,更新结果集
            if node.val in delete:
                if node.left:
                    ans.append(node.left)
                if node.right:
                    ans.append(node.right)
                if node in ans: # 若结果中已经包含 node 为根的树,就把结果集中的 node 删掉。其实可以不要这句,因为后面又 return None 了,加上只是为了逻辑严谨
                   ans.remove(node)
                return None # 若结果集中没有包含 node 为根的树,将 node 置为 None 再返回,相当于删掉 node
            return node # node.val not in delete 的情况

        root = dfs(root) # 可以直接写为 dfs(root)
        return ans

时间复杂度:O(N),其中 N 为树的结点个数。

空间复杂度:O(N)。

如果想把形参 node.left 和 node.right 定义为 left 和 right,思路有些绕:

def dfs(node):
            if not node:
                return

            left, right = node.left, node.right
            dfs(left)
            dfs(right)

            if node.val in delete_set:
                if left and left.val not in delete_set:
                    res.append(left)
                if right and right.val not in delete_set:
                    res.append(right)
            if left and left.val in delete_set:
                node.left = None

            if right and right.val in delete_set:
                node.right = None

作者:amchor
链接:https://leetcode.cn/problems/delete-nodes-and-return-forest/solution/jian-dan-dfs-by-amchor/

前序遍历

如果在递归过程中修改二叉树结构,必须要让父结点接收递归函数的返回值。

可以通过函数参数传递父结点传递的数据,所以可以在前序位置判断是否得到了一个新的根结点。

@ JavaScript

/**
 * @param {TreeNode} root
 * @param {number[]} to_delete
 * @return {TreeNode[]}
 */
var delNodes = function (root, to_delete) {
  let delSet = new Set();
  // 记录森林的根节点
  let res = [];
  if (root == null) return [];
  for (let d of to_delete) {
    delSet.add(d);
  }
  // 定义:输入一棵二叉树,删除 delSet 中的节点,返回删除完成后的根节点
  const doDelete = (root, hasParent) => {
    if (root == null) return null;
    // 判断是否需要被删除
    let deleted = delSet.has(root.val);
    if (!deleted && !hasParent) {
      // 没有父节点且不需要被删除,就是一个新的根节点
      res.push(root);
    }
    // 去左右子树进行删除
    root.left = doDelete(root.left, !deleted);
    root.right = doDelete(root.right, !deleted);
    // 如果需要被删除,返回 null 给父节点
    return deleted ? null : root;
  };
  doDelete(root, false);
  return res;
};

作者:angela-x
链接:https://leetcode.cn/problems/delete-nodes-and-return-forest/solution/qian-xu-bian-li-lai-jie-jue-shan-dian-ch-l3h4/
posted @ 2022-07-17 17:47  Vonos  阅读(49)  评论(0)    收藏  举报