代码随想录 10.28 || 贪心 LeetCode 738.单调递增的数字、968.监控二叉树

738.单调递增的数字

        当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是 单调递增 的。给定一个整数 n,返回 小于或等于 n 的最大数字,且数字呈 单调递增。贪心策略的一般通解,局部最优则全局最优。我们思考如何确定一个单调递增的整数,需要确保其每一对相邻的数字都满足后者大于等于前者。

        当 n[i - 1] > n[i] 时,不符合单调递增数字的要求,此时我们要将 i - 1 位置的数字减 1,再将后面的数字置 9,比如 153, 5 比 3 大,此时我们将 153 变为 149,确保各个数位之间单调递增的同时,还要确保该数字为最大。

class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        string str = to_string(n);
        int flag = str.size();
        
        for (int i = str.size() - 1; i > 0; --i) {
            if (str[i] < str[i - 1]) {
                --str[i - 1];
                flag = i;
            }
        }

        for (int i = flag; i < str.size(); ++i) {
            str[i] = '9';
        }

        return stoi(str);
    }
};

        为了方便访问和改变数字,将整数转换为字符串,这样可以通过下标随机访问各个数位,并且可以通过自减修改数位的数字。从后向前遍历,从数字的低位向高位遍历,这样低位发生变化,不影响高位,如果从前向后遍历,数字的高位发生变化,会影响低位,进而影响结果。stoi 函数将字符串转换为整数,并且能够将字符串开头的 0 字符去除。

968.监控二叉树

        如果想要在全局范围内确保摄像头的数量最少,应该在局部确保每个摄像头监控到足够多的节点。每个节点的监控情况如下(不包含父节点和其本身,因为一个节点总能监视到这两种节点):监控 2 个节点(左右子节点),监控 1 个节点(左 或 右节点),监控 0 个节点(叶子节点)。我们应该尽力避免在叶子节点放置摄像头,保证摄像头都放置在拥有叶子节点的节点上。因此如何判断一个节点有没有左右子树?那一定是后序遍历,先访问其左右子树,才能知道有没有左右子树。确定下使用后序遍历。我们还需要分类状态。

class Solution {
private:
    int result;
    int traversal(TreeNode* cur) {

        // 空节点,该节点有覆盖
        if (cur == NULL) return 2;

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

        // 情况1
        // 左右节点都有覆盖
        if (left == 2 && right == 2) return 0;

        // 情况2
        // left == 0 && right == 0 左右节点无覆盖
        // left == 1 && right == 0 左节点有摄像头,右节点无覆盖
        // left == 0 && right == 1 左节点有无覆盖,右节点摄像头
        // left == 0 && right == 2 左节点无覆盖,右节点覆盖
        // left == 2 && right == 0 左节点覆盖,右节点无覆盖
        if (left == 0 || right == 0) {
            result++;
            return 1;
        }

        // 情况3
        // left == 1 && right == 2 左节点有摄像头,右节点有覆盖
        // left == 2 && right == 1 左节点有覆盖,右节点有摄像头
        // left == 1 && right == 1 左右节点都有摄像头
        // 其他情况前段代码均已覆盖
        if (left == 1 || right == 1) return 2;

        // 以上代码我没有使用else,主要是为了把各个分支条件展现出来,这样代码有助于读者理解
        // 这个 return -1 逻辑不会走到这里。
        return -1;
    }

public:
    int minCameraCover(TreeNode* root) {
        result = 0;
        // 情况4
        if (traversal(root) == 0) { // root 无覆盖
            result++;
        }
        return result;
    }
};

        这里直接引用代码随想录里Carl哥的分类代码(太繁琐,不想敲字)。本题的难点在于:分析使用后序遍历以及分类情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值