本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
给出二叉树的根节点 root,树上每个节点都有一个不同的值。
如果节点值在 to_delete 中出现,我们就把该节点从树上删去,最后得到一个森林(一些不相交的树构成的集合)。
返回森林中的每棵树。你可以按任意顺序组织答案。
示例 1:
 
输入:root = [1,2,3,4,5,6,7], to_delete = [3,5]
输出:[[1,2,null,4],[6],[7]]
示例 2:
输入:root = [1,2,4,null,3], to_delete = [3]
输出:[[1,2,4]]
提示:
- 树中的节点数最大为 1000。
- 每个节点都有一个介于 1到1000之间的值,且各不相同。
- to_delete.length <= 1000
- to_delete包含一些从- 1到- 1000、各不相同的值。
思考以下问题:
- 如何快速判断节点值是否在 toDelete \textit{toDelete} toDelete 中?
- 如果当前节点被删除,但是左儿子(右儿子)没被删除,意味着什么?
其实很简单:
- 把 t o D e l e t e toDelete toDelete 全部丢到一个哈希表 s s s 或位图 b s t bst bst 中,这样可以 O ( 1 ) O(1) O(1) 判断节点值是否在 toDelete \textit{toDelete} toDelete 中。
- 如果当前节点被删除,但左儿子(右儿子)没被删除,意味着左儿子(右儿子)是一棵树的根节点,加入答案。
解法1 后序遍历
写一个 DFS(后序遍历):
- 更新左儿子(右儿子)为递归左儿子(右儿子)的返回值。
- 如果当前节点被删除,那么就检查左儿子(右儿子)是否被删除,如果没被删除,就加入答案。
- 如果当前节点被删除,返回空节点,否则返回当前节点。
- 最后,如果根节点没被删除,把根节点加入答案。
class Solution {
private:
    vector<TreeNode*> ans;
    bitset<1010> bst;
    TreeNode* dfs(TreeNode* root) {
        if (root == nullptr) return nullptr;
        root->left = dfs(root->left);
        root->right = dfs(root->right);
        if (!bst[root->val]) return root;
        if (root->left) ans.push_back(root->left);
        if (root->right) ans.push_back(root->right);
        return nullptr;
    }
public:
    vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) {
        for (int del : to_delete) bst[del] = 1;
        root = dfs(root);
        if (root) ans.push_back(root);
        return ans;
    }
};
解法2 先序遍历
上述解法是先递去,并在归来后解决问题。这里使用先序遍历,在「递」的过程中获取答案,因此可将 r o o t root root 的处理合并到递归函数中。
做法是:设置一个额外的 bool 参数  
     
      
       
       
         i 
        
       
         s 
        
       
         T 
        
       
         r 
        
       
         e 
        
       
         e 
        
       
      
        isTree 
       
      
    isTree 。对一个子树的根结点来说,如果它在要删除的结点之列, 递归左右儿子时  
     
      
       
       
         i 
        
       
         s 
        
       
         T 
        
       
         r 
        
       
         e 
        
       
         e 
        
       
      
        isTree 
       
      
    isTree 就为  
     
      
       
       
         t 
        
       
         r 
        
       
         u 
        
       
         e 
        
       
      
        true 
       
      
    true ,如果它的左右儿子(存在且)不在删除之列,那么就是新树的根。
class Solution { 
public:
    vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) {
        vector<TreeNode*> ans;
        bitset<1010> bst;
        for (int del : to_delete) bst[del] = 1;
        function<TreeNode*(TreeNode*, bool)> dfs = [&](TreeNode* root, bool isTree) -> TreeNode* {
            if (root == nullptr) return nullptr; 
            bool flag = bst[root->val];
            if (isTree && !flag) ans.push_back(root);
            root->left = dfs(root->left, flag);
            root->right = dfs(root->right, flag);
            return flag ? nullptr : root;
        };
        dfs(root, true); 
        return ans;
    }
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)


















