【代码随想录训练营第42期 Day31打卡 贪心Part5 - LeetCode 56. 合并区间 738.单调递增的数字 968.监控二叉树

目录

一、做题心得

二、题目与题解

题目一:56. 合并区间

题目链接

题解:排序+贪心 

题目二:738.单调递增的数字

题目链接

题解:贪心

题目三:968.监控二叉树

题目链接

题解:贪心+二叉树后序遍历 

三、小结


一、做题心得

今天是代码随想录打卡的第31天,迎来了贪心章节的最后部分。个人感觉今天的最后一题监控二叉树还是挺新颖的,巧妙地运用到了贪心思想。

话不多说,直接开始今天的内容。

二、题目与题解

题目一:56. 合并区间

题目链接

56. 合并区间 - 力扣(LeetCode)

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

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

提示:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104
题解:排序+贪心 

这个题也是很经典的重叠区间问题,和昨天的几道打卡题做法差不多,就不详细述说了。

这里需要注意的是:我们存放的结果区间是由不重叠区间以及合并之后的重叠区间构成。

如何合并两重叠区间呢?我们先存放前者区间(注意是有排序之后的)到结果数组里,再对刚存放的前者区间扩充到两区间(前者和后者区间)的范围--只需要右边界取两区间最后边(较大的终止位置)即可。

代码如下:

class Solution {
public:
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        return a[0] < b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        vector<vector<int>> ans;
        vector<int> vec;
        int n = intervals.size();
        sort(intervals.begin(), intervals.end(), cmp);
        ans.push_back(intervals[0]);
        for (int i = 1; i < n; i++) {
            if (intervals[i][0] <= intervals[i - 1][1]) {       //关键(合并重叠区间):当前区间与上一个区间重叠时,只需要在上一个区间的基础上选择两者间较大的end作为结束位置
                ans.back()[1] = max(intervals[i - 1][1], intervals[i][1]);
                intervals[i][1] = max(intervals[i - 1][1], intervals[i][1]);            //更新合并区间后的右边界
            }
            else    ans.push_back(intervals[i]);
        }
        return ans;
    }
};

题目二:738.单调递增的数字

题目链接

738. 单调递增的数字 - 力扣(LeetCode)

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

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

示例 1:

输入: n = 10
输出: 9

示例 2:

输入: n = 1234
输出: 1234

示例 3:

输入: n = 332
输出: 299

提示:

  • 0 <= n <= 109
题解:贪心

这道题不要想着在小于n的数中挨个遍历找到最大的满足单调递增的数字--这肯定还要自定义个函数判断(每个数都进去判断一次肯定会超时的)。

这道题采用贪心的思路。

思路:当发现当前数字 > 下一个数字时(不满足单调递增),就把当前数字 - 1,后面的所有数字都设置成 9。

这个思路是不是很巧妙?你仔细理一下就会发现的确如此。不过我们需要对每一个不满足递增的情况都进行同样的操作,这里我们采用从后往前遍历来实现(从前往后也可以,个人感觉会麻烦一些)。

注意:我们可以设置一个 flag 作为不满足单调递增位置的指标,随着从后往前遍历,不断更新flag,最后统一将flag及其之后的数字变为9。(这样就只需要最后的一次循环即可)

代码如下:

class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        if (n < 10) {               //特殊情况简单考虑
            return n;
        }
        string s = to_string(n);                //数字转化为字符串
        int flag = s.size();            //初始化flag
        for (int i = s.size() - 1; i > 0; i--) {        //从右向左遍历字符串,找到不满足单调递增的位置
            if (s[i - 1] > s[i]) {          //左边数字大于右边数字--当前位置递减
                flag = i;               //更新flag位置
                s[i - 1]--;                 //左边数字-1
            }
        }
        for (int i = flag; i < s.size(); i++) {         //从flag位置开始,到字符串的末尾,将所有的数字都设置为'9'
            s[i] = '9';
        }
        return stoi(s);             //重新转化为数字
    }
};

题目三:968.监控二叉树

题目链接

968. 监控二叉树 - 力扣(LeetCode)

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

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

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

示例 1:

输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。

示例 2:

输入:[0,0,null,0,null,0,null,null,0]
输出:2
解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。


提示:

  1. 给定树的节点数的范围是 [1, 1000]
  2. 每个节点的值都是 0。
题解:贪心+二叉树后序遍历 

这道题难度较大,主要是考虑的情况很多容易乱,我选择直接看题解,它用数字存储状态的方法很巧妙。

贪心策略:从下往上看,实现局部最优--让叶子节点的父节点安摄像头,所用摄像头最少 -- 即后序遍历实现 -- 左右根,实现从下往上遍历。

每个节点可能存在三种状态:(分别用0,1,2表示)

    一.该节点无覆盖:0       (覆盖表示在摄像头范围内--包含父节点,子节点)

    二.该节点有摄像头:1

    三.该节点有覆盖:2             -- 这里不包含该节点有摄像头的情况

知道了以上之后,直接看代码,结合代码里的注释,会比较好理解:

class Solution {
public:
    int ans;
    int traversal(TreeNode* cur) {      // 后序遍历:从下往上看,实现局部最优--让叶子节点的父节点安摄像头,所用摄像头最少

        // 空节点等价于该节点有覆盖
        if (cur == nullptr)     return 2;

        //后序遍历:左右根
        int left = traversal(cur->left);        // 左:递归遍历左子树
        int right = traversal(cur->right);      // 右:递归遍历右子树

        // 情况1:左右子节点都有覆盖
        if (left == 2 && right == 2)    return 0;       //当前节点不需要额外的摄像头来覆盖

        // 情况2:左右子节点中至少有一个无覆盖
        if (left == 0 || right == 0) {
            ans++;                      // 需要在该节点装摄像头
            return 1;       // 当前节点已有摄像头(状态)
        }

        // 情况3:左右子节点中至少有一个有摄像头
        if (left == 1 || right == 1)    return 2;       // 当前节点有覆盖(状态)-- 由子节点的摄像头覆盖

        return -1;      // 理论上不会执行到这里,但为了代码的完整性(只有if,没有else),可以返回一个错误码-1
    }
    int minCameraCover(TreeNode* root) {
        ans = 0;
        if (traversal(root) == 0) {     // 遍历完成后,如果根节点无覆盖,就得在根节点处安装一个摄像头
            ans++;
        }
        return ans;
    }
};

三、小结

贪心章节的打卡到此也就结束了,不过贪心这一块的内容以后也会多加练习。最后,我是算法小白,但也希望终有所获。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值