24暑假算法刷题 | Day31 | 贪心算法 V | LeetCode 56. 合并区间,738. 单调递增的数字,968. 监控二叉树


56. 合并区间

点此跳转题目链接

题目描述

以数组 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

题解

贪心算法解决。这题和 435. 无重叠区间 很像,参考我那题的 笔记 ,可以采用类似的解法:

首先,将这些区间按照左边界从小到大排序。接着,尽量将下一个区间“纳入”当前区间,判断方法也很直观: 下一个区间的左边界 不大于 当前区间的右边界 ,就可以将其纳入当前区间。否则将下一个区间作为新的区间,计数。

代码(C++)

vector<vector<int>> merge(vector<vector<int>> &intervals)
{
    // 贪心算法:先按左边界排序,再依次合并
    auto cmp = [](const vector<int> &a, const vector<int> &b) {
        return a[0] < b[0];
    };
    sort(intervals.begin(), intervals.end(), cmp);

    vector<vector<int>> res;
    int left = intervals[0][0], right = intervals[0][1];
    for (int i = 0; i < intervals.size(); ++i) {
        if (intervals[i][0] > right) {
            res.emplace_back(vector<int>{left, right});
            left = intervals[i][0], right = intervals[i][1];
        } else 
            right = max(right, intervals[i][1]);

        if (i == intervals.size() - 1)
            res.emplace_back(vector<int>{left, right});
    }
    return res;
}

738. 单调递增的数字

点此跳转题目链接

题目描述

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

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

示例 1:

输入: n = 10
输出: 9

示例 2:

输入: n = 1234
输出: 1234

示例 3:

输入: n = 332
输出: 299

提示:

  • 0 <= n <= 109

题解

贪心算法解决。结合所给示例,我们不难想出将 n 转化为单调递增数的一个简单思路:从最低位开始,如果当前位(第 i 位)数字比上一位(第 i + 1 位)数字小,即满足单调递增,则数字不变;否则,第 i 位数字减一、之前各位数字变为9。

例如:332 ➡️ 329 ➡️ 299

因此,我们可以这样将数字从后往前“扫一遍”,记录最后要将数字变为9的起始位置,之后将它们都变为9即可。

代码(C++)

int monotoneIncreasingDigits(int n)
{
    string strN = to_string(n);
    // 从后往前逐位确定
    int flag = strN.size(); // 结果末尾连续9的起始位
    for (int i = strN.size() - 1; i > 0; --i) {
        if (strN[i - 1] > strN[i]) {
            strN[i - 1]--;
            flag = i;
        }
    }
    for (int i = flag; i < strN.size(); ++i)
        strN[i] = '9';
    return stoi(strN);
}

968. 监控二叉树

点此跳转题目链接

题目描述

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

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

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

示例 1:

在这里插入图片描述

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

示例 2:

在这里插入图片描述

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

提示:

  1. 给定树的节点数的范围是 [1, 1000]
  2. 每个节点的值都是 0。

题解

比较巧妙的贪心算法利用。结合给出的示例,我们不难想出一种最节省摄像头的方案:如果如果当前节点、子节点、父节点都没有被监控范围覆盖,则在当前节点添加一个摄像头即可。例如,要将监控下面这棵单侧树的所有6个节点:

          0 
         /
        0
       /
      0
     /
    0
   /
  0
 /
0

只需要在第1和第5个节点处添加摄像头即可(用 ‘2’ 表示摄像头):

          0 
         /
        2
       /
      0
     /
    0
   /
  2
 /
0

同时可以发现,我们总是不需要在叶子节点安装摄像头的(除非是平凡树),即应该从叶子节点开始向上贪心地尽可能少地安装摄像头。因此,应当需要一种自下而上的算法,自然联想到后序遍历。考虑递归方法解决,基本框架应该是:

int traversal(TreeNode* cur) {
    if (终止条件) return ;

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

    逻辑处理                            // 中
    return ;
}

返回一个整数,表示当前节点此时的状态——无外乎3种:

  • 未覆盖( 0
  • 有覆盖但不是摄像头( 1
  • 是摄像头( 2

根据子节点的状态,可以确定当前节点是否安装摄像头:

class Solution
{
private:
    int count = 0;

    /// @brief 从叶子节点自下而上后序遍历,确定摄像摄像头的位置
    /// @param cur
    /// @return 当前节点未覆盖(0),被覆盖但无摄像头(1),有摄像头(2)
    int traverse(TreeNode *cur)
    {
        if (!cur)
            return 1; // 空节点认为被覆盖但无摄像头

        int left = traverse(cur->left);   // 左
        int right = traverse(cur->right); // 右

        // 中
        if (!left || !right) // 左右孩子中有没被覆盖的
        { 
            count++;
            return 2; // 放摄像头
        }
        else if (left == 2 || right == 2) // 左右孩子中有摄像头
            return 1;                     // 当前节点肯定被覆盖,没必要放摄像头
        else                              // 左右孩子都被覆盖但都不是摄像头
            return 0;                     // 当前节点(暂时)不覆盖,交给父节点解决
    }

public:
    int minCameraCover(TreeNode *root)
    {
        if (!traverse(root))
            return count + 1;
        else
            return count;
    }
};

⚠️ 注意两处细节:

  • 空节点被认为是“被覆盖但无摄像头”,因为这样才能确保其父节点(即叶子节点)不会安装多余的摄像头
  • 最后要检查一下根节点 root 是否被覆盖,如没有还需要给它自己再安一个摄像头
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值