第八章 贪心算法part06 738. 单调递增的数字 968. 监控二叉树

第三十七天| 第八章 贪心算法part06 738. 单调递增的数字 968. 监控二叉树

一、738. 单调递增的数字

  • 题目链接:https://leetcode.cn/problems/monotone-increasing-digits/

  • 题目介绍:

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

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

      示例 1:

      输入: n = 10
      输出: 9
      
  • 思路:

    • 总体的思路是:
      • 采用后序遍历的方式,如果前一位大于当前这一位,那么前一位就应该减减,然后当前这一位要变成9,以保证单调递增且是最大的;
      • 另一个需要注意的是:只要出现前一位大于后一位的情况,那么从当前位置开始,后面的全部要变为9,为什么呢?
        • 例如:3232
        • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  • 代码:

class Solution {
    public int monotoneIncreasingDigits(int n) {
        // 画个图就知道应该是从后向前遍历
        String str = "" + n;
        char[] ch = str.toCharArray();
        int flag = ch.length;
        for (int i = ch.length - 1; i > 0; i--) {
            if (ch[i] < ch[i - 1]) {
                ch[i - 1]--;
                flag = i;
            }
        }
        for (int i = flag; i < ch.length; i++) {
            ch[i] = '9';
        }
        int result = Integer.parseInt(new String(ch));
        return result;
    }
}

二、968. 监控二叉树(难,不好理解)

  • 题目链接:https://leetcode.cn/problems/binary-tree-cameras/

  • 题目介绍:

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

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

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

    • 示例 1:

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

    • 这道题目首先要想,如何放置,才能让摄像头最小的呢?

      从题目中示例,其实可以得到启发,我们发现题目示例中的摄像头都没有放在叶子节点上!

      这是很重要的一个线索,摄像头可以覆盖上中下三层,如果把摄像头放在叶子节点上,就浪费的一层的覆盖。

      所以把摄像头放在叶子节点的父节点位置,才能充分利用摄像头的覆盖面积。

      • 那为什么不从头结点开始看起呢,为啥要从叶子节点看呢?
        • 因为头结点放不放摄像头也就省下一个摄像头, 叶子节点放不放摄像头省下了的摄像头数量是指数阶别的。
    • 贪心就贪在这里:

      • 我们需要从叶子节点开始看,而不是从根节点出发
        • 这是因为减少根节点放置摄像头的个数,只能减少一个;而根节点将会省下指数级别的(因为显而易见,叶子节点的个数会很多)
    • 所以本题大体思路就是**从下到上,采用后序遍历的方式,先给叶子节点父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点。**

      • (1)如何隔两个节点放一个摄像头?

        • 此时需要状态转移,这里的状态转移并不是动态规划中的状态转移公式,而是声明不同的状态:
        • 每个节点的状态有以下几种可能:
          • 0:该节点无覆盖
          • 1:本节点有摄像头
          • 2:本节点有覆盖
            • 注意:本节点无摄像头的情况已经包含在以上三种情况中了
      • (2)空节点是什么状态呢?

        • 显然,它肯定不是“本节点有摄像头”,那它到底是“无覆盖”还是“有覆盖”呢?
          • 如果它是“无覆盖”,那么叶子节点就必须安摄像头,也就意味着和我们的贪心思路相违背。
          • 因此空节点必须是“有覆盖”
      • (3)按照递归三部曲的方式处理二叉树的遍历

        • 第一步:确定递归函数的参数和返回值

          • // 返回的是节点的状态,传入的是根节点
            public int traversal(TreeNode root) {
                
            }
            
        • 第二步:确定递归终止的条件

          • 遇到null节点,向上返回2
        • 第三步:单层递归的逻辑(有四种情况)

          • 注意:情况一二三都是在递归中判断的,情况四因为是整颗二叉树的根节点,所以在主函数中判断即可
          • 情况1:左右节点都有覆盖
            • 左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态了。
            • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
            • 你可能会有一个疑问,该节点的左右子孩子是“覆盖”的状态,为什么该节点不可能是“有摄像头”呢?
              • 解释如下:
                • 按照上述的逻辑,一个节点为2,只有两种情况,一种是它是null,另一种就是:它下面有一个摄像头
                  • 为什么不是它的上一层有摄像头呢?因为我们是从下向上的后序遍历,因此左右子孩子是2,说明一定被覆盖掉了,怎么覆盖掉的,说明前面有摄像头(例如:对于叶子节点而言,如果一个叶子节点有摄像头,就会浪费它下一层的覆盖范围,所以对于叶子节点就要在它的父节点添加摄像头)
          • 情况2:左右节点至少有一个无覆盖的情况
            • 这种情况比较好理解,如果左右子孩子存在没覆盖到的情况,那么这个节点一定要安摄像头
          • 情况3:左右节点至少有一个有摄像头
            • 这种情况也比较好理解,左右孩子节点有一个有摄像头了,那么其父节点就应该是2(覆盖的状态)
            • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
          • 情况4:头结点没有覆盖
            • 前面三种情况都比较容易想到,情况四容易被忽略
            • 以上都处理完了,递归结束之后,可能头结点 还有一个无覆盖的情况,如下图:
            • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
    • 注意:

      • 这个节点状态一定是后序遍历的时候,当前节点的在状态,一定要是从下向上看当前的状态(这个要理解)
      • 比如,你在后序遍历的时候,发现该节点的左右子孩子都是2,那么该节点就应该等于0,然后该节点的父节点就应该放一个摄像头,你这个时候就会想,不是放摄像头了吗?这个0是不是要改成2啊,不是的,你只能返回根节点的值,不要去改变前面遍历过的节点的值,前面遍历节点的值是为了让你推算出根节点的值。
  • 代码:

class Solution {
    int result = 0;
    public int minCameraCover(TreeNode root) {
        if (traversal(root) == 0) result++;
        return result;
    }

    public 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;
        }
        if (left == 0 || right == 0) {
            result++;
            return 1;
        }
        if (left == 1 || right == 1) {
            return 2;
        }
        return -1;
    }
}
  • 代码很简洁,思路要理清
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值