【C/C++】LCP 52. 二叉搜索树染色

358 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第23天,点击查看活动详情


题目链接:LCP 52. 二叉搜索树染色

题目描述

欢迎各位勇者来到力扣城,本次试炼主题为「二叉搜索树染色」。

每位勇士面前设有一个 二叉搜索树 的模型,模型的根节点为 root,树上的各个节点值均不重复。初始时,所有节点均为蓝色。现在按顺序对这棵二叉树进行若干次操作, ops[i] = [type, x, y] 表示第 i 次操作为:

  • type 等于 0 时,将节点值范围在 [x, y] 的节点均染蓝
  • type 等于 1 时,将节点值范围在 [x, y] 的节点均染红 请返回完成所有染色后,该二叉树中红色节点的数量。

注意:

  • 题目保证对于每个操作的 xy 值定出现在二叉搜索树节点中

提示:

  • 1二叉树节点数量1051 \leqslant 二叉树节点数量 \leqslant 10^5
  • 1ops.length1051 \leqslant ops.length \leqslant 10^5
  • ops[i].length == 3
  • ops[i][0] 仅为 0 or 1
  • 0ops[i][1]ops[i][2]1090 \leqslant ops[i][1] \leqslant ops[i][2] \leqslant 10^9
  • 0节点值1090 \leqslant 节点值 \leqslant 10^9

示例 1:

1649833948-arSlXd-image.png

输入:root = [1,null,2,null,3,null,4,null,5], ops = [[1,2,4],[1,1,3],[0,3,5]]
输出:2
解释:
第 0 次操作,将值为 234 的节点染红;
第 1 次操作,将值为 123 的节点染红;
第 2 次操作,将值为 345 的节点染蓝;
因此,最终值为 12 的节点为红色节点,返回数量 2

示例 2:

1649833763-BljEbP-image.png

输入:root = [4,2,7,1,null,5,null,null,null,null,6]
ops = [[0,2,2],[1,1,5],[0,4,5],[1,5,7]]
输出:5
解释:
第 0 次操作,将值为 2 的节点染蓝;
第 1 次操作,将值为 1245 的节点染红;
第 2 次操作,将值为 45 的节点染蓝;
第 3 次操作,将值为 567 的节点染红;
因此,最终值为 12567 的节点为红色节点,返回数量 5

题意整理

对一棵 二叉搜索树 上的节点进行区间染色,保证树上的各个节点值均不重复。且刚开始时所有节点均为蓝色,有两种染色操作,将节点值范围在 [x, y] 的节点均染成蓝色或者红色。问最后红色节点的个数。

解题思路分析

虽然该题描述是对二叉树进行染色,但实际上跟二叉树没有多大关系,我们只需要将树上的节点映射到数组中,然后按照题目要求对数组进行区间修改和区间查询即可。

该题难点在于:

  1. 节点数量在 10510^5 以内,但节点值却在 10910^9 以内,很明显我们需要对节点值进行离散化操作。
  2. 染色操作次数在 10510^5 以内,如果每次都对 10510^5 个节点进行染色,显然暴力是行不通的,会 TLE 超时。

方法一:线段树

由于题目是区间覆盖问题,这时候我们考虑采用 线段树 来进行维护,最后进行区间查询即可。需要注意的是在使用线段树模板之前我们需要对每个节点进行离散化处理,将节点值 [1,109][1,10^9] 映射到 [1,105][1,10^5],然后套用线段树模板即可。

方法二:反向染色( set 集合)

由于对每个节点染色时会覆盖之前的颜色,所以我们只用 考虑每个节点最后一次染色操作即可 ,也就是所谓的反向染色,在反向染色操作中每次染色都能确定区间中部分节点的颜色,将这些确定的节点从点集合中移除,如果染的是红色记录答案即可。那么能实现高效率集合操作的 set 集合容器是我们的首选。

具体实现

  1. 遍历二叉树,将树上的每个节点值放入 set 集合中。
  2. 反向遍历染色操作,每次将集合中位于染色区间里节点取出并记录红色节点个数。

这里使用到 set 集合容器封装的 lower_bound() 方法,每次返回指向大于等于染色左区间的第一个元素的迭代器。同时还需要判断该元素值是否小于染色右区间。

复杂度分析

  • 时间复杂度:O((n+q)log2n)O((n + q)\log_2 n) ,其中 n 是节点数量,q 是询问数量。因为每个点只会被删掉一次,set 集合容器封装的 lower_bound() 方法采用二分查找,每次查找节点时间为 O(log2n)O(\log_2 n) ,所以总的时间复杂度为 O((n+q)log2n)O((n + q)\log_2 n)
  • 空间复杂度:O(n+q)O(n + q),其中 n 是节点数量,q 是询问数量。

代码实现

由于 方法一:线段树 方法为模板代码,不做代码实现。仅对 方法二:反向染色( set 集合) 做代码实现

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
private:
    //s集合用于存储二叉树的节点值
    set<int> s;
    //遍历二叉树,存放节点值
    void dfs(TreeNode* root){
        s.insert(root->val);
        if(root->left != NULL) dfs(root->left);
        if(root->right != NULL) dfs(root->right); 
    }
public:
    int getNumber(TreeNode* root, vector<vector<int>>& ops) {
        //初始化清空s集合
        s.clear();
        //如果根节点为空,表示树为空,直接返回0
        if(root == NULL) return 0;
        //遍历二叉树,将树上的每个节点存放到set集合中
        dfs(root);
        //m为操作次数
        int m = ops.size();
        //RedNum为红色节点个数
        int RedNum = 0;
        //倒叙遍历,因为节点颜色取决于最后一次染色
        for(int i = m - 1; i >= 0; i--){
            //l为染色左区间,r为染色右区间
            int l = ops[i][1];
            int r = ops[i][2];
            //iter为set集合的迭代器,调用lower_bound(l)方法找到集合中大于等l的第一个元素的迭代器
            auto iter = s.lower_bound(l);
            //如果能够找到集合中满足大于等于l,并且小于等于r的节点值就将其删除
            while(iter != s.end() && *iter <= r){
                s.erase(iter);
                //如果该次操作为染红,则记录红色节点个数
                if(ops[i][0] == 1) RedNum++;
                iter = s.lower_bound(l);
            }
        }
        return RedNum;
    }
};

总结

该题虽然很明显是区间覆盖问题,需要用到线段树维护,对于会和不会线段树的人来说完全是两道题。但是该题巧妙在还可以利用反向染色思想和 set 集合中的 lower_bound() 方法相结合来完成,不仅避免了冗长的线段树代码模板,还能使我们跳出了刻板的做题思维,并不是遇到区间覆盖问题就一定使用线段树来维护的思想。


结束语

今天是世界读书日。知识改变命运,从来不是一句空话。你从书中学到的知识、增长的涵养,会一点一滴地滋养你、改变你,让你获得冲破阻碍的力量。腹有诗书,脚下自会有路。