1110. 删点成林 - Delete Nodes And Return Forest

🎯 问题描述(来源于LeetCode)

描述:
给定二叉树的根节点 root,以及一个要删除的节点值列表 to_delete。
删除所有值为 to_delete 中的节点后,剩下的节点会形成若干棵不相交的子树(森林)。
返回森林中所有子树的根节点列表(顺序任意)。

说明:

  • 删除一个节点会将其左右子树断开,分别成为新的树。
  • 被删除的节点本身不保留。
  • 森林中每棵树的根节点是原树中未被删除且父节点被删除(或原根节点)的节点。

示例:

  • 示例 1:
1
2
3
输入: root = [1,2,3,4,5,6,7], to_delete = [3,5]
输出: [[1,2,null,4],[6],[7]]
解释: 删除节点3和5后,剩下三个子树:以1为根(1-2-4),以6为根,以7为根。
  • 示例 2:
1
2
输入: root = [1,2,4,null,3], to_delete = [3]
输出: [[1,2,4]]

💻 解题思路

思路1:DFS + 传递父节点删除状态

算法步骤

  1. 将待删除列表转为集合 to_del 以提高查找效率。

  2. 定义递归函数 dfs(node, parent_deleted):

    • 参数 node:当前遍历的节点。
    • 参数 parent_deleted:布尔值,表示父节点是否被删除。
    • 返回值:处理后的当前节点(若当前节点被删除则返回 None,否则返回节点本身)。
  3. 递归终止:若 node 为 None,返回 None。

  4. 判断当前节点是否被删除:cur_deleted = node.val in to_del。

  5. 判断是否加入森林:

    • 如果父节点被删除(parent_deleted == True)且当前节点不被删除(cur_deleted == False),则当前节点成为新树的根,将其加入结果列表 res。
  6. 递归处理左右子树:

    • node.left = dfs(node.left, cur_deleted)
    • node.right = dfs(node.right, cur_deleted)
      将当前节点的删除状态传递给子节点。
  7. 返回值:

    • 若当前节点被删除,返回 None,否则返回当前节点(其左右子节点已更新)。
  8. 启动递归:从根节点开始,初始父节点视为被删除(parent_deleted = True),因为根节点没有父节点,相当于其父节点被删除,这样如果根节点不被删除,它会自动加入森林。

思路1:代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
from typing import List, Optional

class Solution:
def delNodes(self, root: Optional[TreeNode], to_delete: List[int]) -> List[TreeNode]:
to_del = set(to_delete)
res = []

def dfs(node, parent_deleted):
if not node:
return None
cur_deleted = node.val in to_del
if parent_deleted and not cur_deleted:
res.append(node)
node.left = dfs(node.left, cur_deleted)
node.right = dfs(node.right, cur_deleted)
return None if cur_deleted else node

dfs(root, True)
return res

思路1:📊 性能分析

  • 时间复杂度:O(n)O(n),每个节点访问一次,集合查找 O(1)O(1)。
  • 空间复杂度:O(n)O(n),递归栈深度为树高 O(h)O(h),结果列表存储最多 nn 个根节点,总体 O(n)O(n)。

思考

  • 本题的关键是自顶向下传递父节点删除状态,并根据该状态决定当前节点是否成为新树的根。
  • 利用后序遍历断开子节点连接:当前节点可能被删除,但其子节点仍需要继续处理,因此先递归再更新指针。
  • 也可以使用 BFS 层序遍历,但 DFS 更简洁。
  • 注意:parent_deleted 初值为 True,确保根节点若不被删除,会被加入结果集。