代码随想录day37 | 贪心算法P6 | ● 738 ● 968 ● 总结

738.单调递增的数字 

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

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

示例 1:

输入: n = 10
输出: 9

示例 2:

输入: n = 1234
输出: 1234

示例 3:

输入: n = 332
输出: 299

思路

首先想到暴力法,超时

暴力(超时)

class Solution {
    public int monotoneIncreasingDigits(int n) {
        if(judge(n)){
            return n;
        }else{
            for(int i = n-1; i > 0; i--){
                if(judge(i)){
                    return i;
                }
            }
        }
        return 0;
    }
    public boolean judge(int n){
        String s = String.valueOf(n);
        char [] c = s.toCharArray();
        for(int i=1; i < c.length; i++){
            if(c[i] >= c[i-1]){
                continue;
            }else{
                return false;
            }
        }
        return true;
    }
}

贪心

贪心策略;拿一个两位的数字来举例。

例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]--,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。

然后判断遍历方向,从前往后还是从后往前。

从前往后会出现如下问题:

如332:变化如下 332 -> 329 -> 将第二个3 -- 变为2 导致它小于第一个3了。

这是因为从前往后没用到上次比较得出的结果,从后往前:332 -> 329 -> 299

class Solution {
    public int monotoneIncreasingDigits(int n) {
        if(judge(n)){
            return n;
        }else{
            String s = String.valueOf(n);
            char [] c = s.toCharArray();
            int start = 0;
            for(int i = c.length - 1; i > 0; i--){
                if(c[i-1] > c[i]){
                    c[i-1] = (char)(c[i-1] - 1);
                    start = i;
                }
            }
            for(int i = start; i<c.length; i++){
                c[i] = '9';
            }
            return Integer.parseInt(String.valueOf(c));
        }
    }
    public boolean judge(int n){
        String s = String.valueOf(n);
        char [] c = s.toCharArray();
        for(int i=1; i < c.length; i++){
            if(c[i] >= c[i-1]){
                continue;
            }else{
                return false;
            }
        }
        return true;
    }
}

总结

本题只要想清楚个例,例如98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]减一,strNum[i]赋值9,这样这个整数就是89。就可以很自然想到对应的贪心解法了。

想到了贪心,还要考虑遍历顺序,只有从后向前遍历才能重复利用上次比较的结果。

最后代码实现的时候,也需要一些技巧,例如用一个flag来标记从哪里开始赋值9。

968.监控二叉树 

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

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

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

示例 1:

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

示例 2:

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

思路

首先明确 如何放置摄像头才能让总体数量最小:

可以发现,示例中的摄像头都没有放在叶子节点上,摄像头可以覆盖上中下三层,如果放在叶子节点上就会浪费一层,所以应当把摄像头放在叶子节点的父节点位置。

那么为什么不从头节点开始看,而是从叶子节点看呢?头节点不放 只能省下一个,而叶子节点不放,省下的摄像头是指数级别的。

所以,从下往上看,局部最优:让叶子节点的父节点安放摄像头,所用摄像头最少,整体最优:全部摄像头最少。

此时,大概思路:从下往上,先给叶子节点的父节点安放摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点。

那么剩下两个难点:

        1、二叉树从下往上遍历

        2、如何隔两个节点放一个摄像头

1:后序遍历,左右中,并且因为需要搜索整棵树,采用递归函数搜索整棵树的写法。

2:利用状态转移的公式。记录三种状态,0-无覆盖,1-有摄像头,2-有覆盖。

首先 空节点只能是有覆盖状态,因为:为了让摄像头数量最少,我们要尽量让叶子节点的父节点安装摄像头,这样才能摄像头的数量最少。那么空节点不能是无覆盖的状态,这样叶子节点就要放摄像头了,空节点也不能是有摄像头的状态,这样叶子节点的父节点就没有必要放摄像头了,而是可以把摄像头放在叶子节点的爷爷节点上。

所以空节点的状态只能是有覆盖,这样就可以在叶子节点的父节点放摄像头了

递归后序遍历,终止条件即为遇到空节点,同时返回状态值 2 有覆盖

然后利用递归搜索整棵树的模板,定义代表左右子树状态值的int数 left right,进行后序遍历、

单层递归逻辑:

有如下四类情况:(三种状态,0-无覆盖,1-有摄像头,2-有覆盖。)

①当left为2 right也为2时,此时root应返回 0 无覆盖

②当left right至少有一个为 0 无覆盖时,此时root返回 1 有摄像头

③当left right 至少有一个为1 有摄像头时,此时root返回 2 有覆盖

④当根节点 状态为 0 res++;

注意上述顺序种 ③中的部分逻辑 如(1,0)左节点有摄像头,右节点无覆盖

提前在②中 处理,因为需满足所有节点被覆盖。

 代码

class Solution {
    public int res = 0;
    public int minCameraCover(TreeNode root) {
        //根节点无覆盖,此时需在根节点加一个
        if(tracing(root) == 0) {
            res++;
        }
        return res;
    }
    /*
    0 无覆盖
    1 有摄像头
    2 有覆盖
    */
    public int tracing(TreeNode root){
        //空结点,默认有覆盖
        if(root == null) return 2;

        int left = tracing((root.left));
        int right = tracing(root.right);
        //左右状态共九种
        //左右都有覆盖,此时节点为无覆盖
        if(left == 2 &&  right == 2){
            //(2,2)
            return 0;
        }else
        //左右至少有一个是无覆盖的,那么应该返回1,在此节点处放摄像头
        if(left == 0 || right == 0){
            // (0,0) (0,1) (0,2) (1,0) (2,0)
            // 状态值为 1 摄像头数 ++;
            res++;
            return 1;
        }else{
            //左右至少有一个是有摄像头的,此时节点为有覆盖
            //(1,1) (1,2) (2,1)
            return 2;
        }
    }
}

总结

贪心思路:局部最优:让叶子节点的父节点安摄像头,所用摄像头最少,整体最优:全部摄像头数量所用最少!

求解:后序遍历整棵树 + 状态转移 + 单层递归情况分解

贪心总结 

首先贴一个图: 来自代码随想录 (programmercarl.com)

开始复习以往做过的题目,计划通过类似方式对做过的题目进行总结归纳。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值