代码随想录算法训练营第31天 | LeetCode56.合并区间、LeetCode738.单调递增的数字、LeetCode968.监控二叉树

目录

LeetCode56.合并区间

LeetCode738.单调递增的数字 

LeetCode968.监控二叉树 


LeetCode56.合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 

思路:这到题和昨天讲的三道题思路很像,如果昨天的搞明白了,那么其实这道题非常简单了。

关键点在与重叠区间如何处理。

首先我们知道需要排序,下面是按照区间的起始坐标从小到大的排序。

可以在区间里面更新,也可以在最终答案里面更新。

如果选择在原区间里面更新,那么这里就要清楚了,当遇到重叠区间的时候,需要将新的起始下标和终止下标更新,然后在遇到没有重叠的时候,证明前面的区间已经满足了合并完成的条件,将其加入result中。

这里注意跳出循环后需要将最后一个元素区间加入result中,因为它更新完成,但是还没来得及添加。

    static bool cmp(vector<int>& a, vector<int>& b){
        return a[0] < b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        vector<vector<int>> result;//记录最终结果
        sort(intervals.begin(), intervals.end(), cmp);//排序
        for(int i = 1; i < intervals.size(); i ++){
            if(intervals[i][0] > intervals[i - 1][1]){
                result.push_back(intervals[i - 1]);//当遇到未重叠部分,将前一个区间加入result中
            }else{//两区间重叠,开始合并
                intervals[i][0] = min(intervals[i][0], intervals[i - 1][0]);//合并区间起始点取两者起始点的最小值
                intervals[i][1] = max(intervals[i][1], intervals[i - 1][1]);//合并区间终止点取两者终止点的最大值
            }
        }
        result.push_back(intervals[intervals.size() - 1]);//将最后一个区间加入result中
        return result;
    }

如果选择在结果区间里面更新,那么就从第一个元素开始,先将第一个元素放入结果数组中,然后开始遍历,当遇到重叠的区间就进行最大终点下标的更新。这里为什么不更新起始坐标是因为本身就是按照起始下标排的顺序,也就是说重叠的区间的起始下标一定是小于等于前面一个元素的起始下标,所以没有必要更新。然后如果没有重叠那就直接加入结果数组即可。

    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        vector<vector<int>> result;//记录最终结果
        //排序这里使用了lambda表达式
        sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b){return a[0] < b[0];});
        result.push_back(intervals[0]);//首先将第一个区间放入result
        for(int i = 1; i < intervals.size(); i ++){
            if(result.back()[1] >= intervals[i][0]){
                result.back()[1] = max(result.back()[1], intervals[i][1]);//合并区间的终止点取两个区间的终点值的最大值
            }else{
                result.push_back(intervals[i]);//没有重叠就将区间直接加入result
            }
        }
        return result;
    }

时间复杂度:O(nlogn)

空间复杂度:O(logn)

LeetCode738.单调递增的数字 

当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。

给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增

思路:这里如果采用暴力求解是会超时的,因为如果一个一个数值去找,对每一个数值进行判断,数小还好说,一旦大了就会超时,所以暴力求解是不可取的。

这里需要明白一点,对于一个数,如何求比他小的最大单调递增数呢?

比如98,我们可以手动计算可知,89是比它小的最大单调递增数。可以这样看,从后往前进行遍历,当遇到9的时候,发现它比8要大,为了求最大,那么就使得9减去1,同时使8变为9,这样能够取到对应的数。

那为什么是从后往前遍历呢?因为从前往后遍历会出问题,比如332,从前往后就会是329,显然这个结果有问题(3比2大了),而如果是从后往前,那么就会是332 => 329 => 299,这个结果是正确的。其实原因就在于从前往后遍历没有利用到更新的元素,而从后往前遍历的话前一个元素能够利用后一个元素更新过的值作比较,方便最终结果的更新得出。

    int monotoneIncreasingDigits(int n) {
        string str = to_string(n);
        int flag = str.size();//标记需要进行赋值9的起始坐标
        for(int i = str.size() - 1; i > 0; i --){//注意这里是从后向前进行遍历
            if(str[i - 1] > str[i]){//当前一位数字大于该位数字时
                flag = i;//标记好置9的起始下标
                str[i - 1] --;//前一位数字减一
            }
        }
        for(int i = flag; i < str.size(); i ++){
            str[i] = '9';//将从flag开始的后续元素均置9
        }
        return stoi(str);
    }

时间复杂度:O(n)

空间复杂度:O(n)

LeetCode968.监控二叉树 

给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

思路:这道题题干简短,但其实比较难。

首先我们要知道能够在什么地方装摄像头。可以发现,没有在也叶子结点装摄像头的情况出现,因为这样会浪费一层的监控;但是对于叶子结点也要监控,所以是需要对叶子结点的父结点装摄像头。

然后我们再想,采用什么样的遍历方式呢。上面分析可以知道,从叶子结点的父节点开始,那么其实就是从低到高的一种遍历,所以可以采用左右中的后序遍历方式。为什么不从根节点开始呢?因为根节点装不装摄像头也就是差1的数量,而对于叶子结点来说会是根节点的指数倍数量,所以从底下开始。

接着我们来想一想有一个叶子结点有哪些状态。可以得知,无覆盖有摄像头有覆盖(被其他摄像头监控着)这三种情况。为什么没有无摄像头这种情况呢,因为无摄像头也就是说这里没有摄像头在该结点,但是它可能处于无覆盖或者有覆盖两种状态中的一种,也就是说已经包括在上面状态中了,所以没有包括无摄像头这种情况。

这里使用0来表示无覆盖1表示有摄像头2表示有覆盖

那么其实就有一个大体框架了,可以开始构造代码了。

结束条件怎么写呢?也就是说遇到空结点该返回什么呢?

假如返回0,那么就说明该地方没有摄像头监控,则需要在叶子结点装摄像头,这是会造成摄像头泛滥的,该情况不太现实;

假如返回1,那么就说明空结点有摄像头,那叶子结点的状态就会变为2(有覆盖),那就没有必要在叶子结点的父节点装摄像头了,应该在叶子结点的爷爷结点装,显然也不适合我们上面分析的情况。

因此最后得出,空结点的地方返回状态2(有覆盖)

于是接下来我们可以处理中间结点的情况了,主要有四种:

第一种是中间结点的左右孩子的状态为2(有覆盖),那么自然,中间结点的状态为0(无覆盖);

第二种是中间结点的左右孩子至少有一个孩子的状态为0(无覆盖),那么这也很好理解,孩子没有监控上,那么就需要在这里安装一个摄像头,使这个没有被监控的孩子得到监控,同时这个结点的状态为1(有摄像头);

第三种是中间结点的孩子至少有一个孩子的状态为1(有摄像头),因为摄像头能够监控到其父结点,因此中间结点的状态为2(有覆盖);

第四种情况是当所有递归结束,返回到根节点时,如果说根节点的状态为0(无覆盖),那么就需要在根节点处安装一个摄像头,监控根节点。

至此,全部完结,代码如下。

    int result;
    int traversal(TreeNode* root){
        //如果是空结点,那么返回2(有覆盖)
        if(root == NULL) return 2;

        int left = traversal(root -> left);//左
        int right = traversal(root -> right);//右

        //开始中间结点逻辑处理

        //这里是中间结点的左右结点都有覆盖,那么中间结点的状态是0(无覆盖)
        if(left == 2 && right == 2) return 0;

        //这里是中间结点的左右结点中存在一个结点的状态为0,也就是说无覆盖
        //那么这里中间结点就应该安装一个摄像头,使得其无覆盖的子节点状态变为2(有覆盖)
        if(left == 0 || right == 0){
            result ++;
            return 1;
        }

        //这里是中间结点的左右结点中存在一个结点是有摄像机的状态1,
        //因为摄像机可以监控其父对象,所以中间结点的状态为2(有覆盖)
        if(left == 1 || right == 1) return 2;

        return -1;//整个递归过程该位置是不会到达的,也就是说不会返回-1,但是这个函数需要一个返回值
    }
    int minCameraCover(TreeNode* root) {
        result = 0;
        //这里的根节点可能会是一个无覆盖0的状态,因此这里需要有一个摄像头
        if(traversal(root) == 0){
            result ++;
        }
        return result;
    }

精简过后的代码如下所示。

    int result;
    int traversal(TreeNode* root){
        if(root == NULL) return 2;
        int left = traversal(root -> left);//左
        int right = traversal(root -> right);//右
        if(left == 2 && right == 2) return 0;
        else if(left == 0 || right == 0){
            result ++;
            return 1;
        }else  return 2;
    }
    int minCameraCover(TreeNode* root) {
        result = 0;
        //这里的根节点可能会是一个无覆盖0的状态,因此这里需要有一个摄像头
        if(traversal(root) == 0){
            result ++;
        }
        return result;
    }

时间复杂度:O(n)

空间复杂度:O(n)

感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。

如果有什么问题欢迎评论区讨论!

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值