【树】力扣1110:删点成林
给出二叉树的根节点 root,树上每个节点都有一个不同的值。
如果节点值在 to_delete 中出现,我们就把该节点从树上删去,最后得到一个森林(一些不相交的树构成的集合)。
返回森林中的每棵树。你可以按任意顺序组织答案。
示例:
输入: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/