Day 37 738.单调递增的数字 968.监控二叉树

文章讲述了如何使用贪心策略解决两个问题:给定一个整数N,找到小于或等于N的最大单调递增整数;以及在二叉树中安装摄像头以最小化覆盖所有节点所需的摄像头数量。作者通过实例和逻辑分析展示了如何应用贪心思想来解决问题。
摘要由CSDN通过智能技术生成

单调递增的数字

给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增

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

示例 1:

  • 输入: N = 10
  • 输出: 9

示例 2:

  • 输入: N = 1234
  • 输出: 1234

示例 3:

  • 输入: N = 332
  • 输出: 299

说明: N 是在 [0, 10^9] 范围内的一个整数。

​ 按照案例分析,最后的结果大概率是呈"x999"的形式展示出来:

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

​ 接下来讨论遍历方向:

​ 从前向后遍历的话,遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]减一,但此时如果strNum[i - 1]减一了,可能又小于strNum[i - 2];

​ 例如332按照从前往后遍历的逻辑得到329,不满足递增条件,所以从后往前:

​ 从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299;

​ 这道题的贪心思路的局部最优即是找到strNum[i - 1] > strNum[i]的情况,整体最优即是找到所有的情况并赋值为9;

	int increasingNums(int n){
        string strNum= to_string(n);
        for(int i = strNum.size() - 1; i >  0; i--){
            if(strNum[i - 1] > strNum[i]){
                strNum[i - 1]--;
                strNum[i] = '9';
            }
        }
        return stoi(strNum);
    }

​ 乍一看没毛病,但实际这么写会出问题;

​ 比如1000,按照这个比较逻辑最后得到的是900,而不是999;这时候需要一个index来记录:

	int increasingNums(int n){
        string strNum= to_string(n);
        int index = strNum.size();
        for(int i = strNum.size() - 1; i >  0; i--){
            if(strNum[i - 1] > strNum[i]){
                strNum[i - 1]--;
               	index = i;
            }
        }
        for(int i = index; i < strNum.size(); i++){
            strNum[i] = '9';
        }
        return stoi(strNum);
    }

监控二叉树

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

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

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

示例 1:

img

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

示例 2:

img

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

提示:

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

​ 由题意可知,理论上来说每个摄像头至多可以监控四个节点,包括父节点、自身以及叶子节点,直观观察树形结构可以看到每个摄像头对应的有三层;

​ 由于二叉树的结构原因,节点数是随着树的深度呈指数级增长,所以优化摄像头数量最优思路一定是优先从叶子节点向上寻找;

​ 即以后序遍历的方式向上遍历,左右中的顺序,将叶子节点的信息返回给父节点;

​ 贪心思路:局部最优:让所有叶子节点的父节点安上摄像头;整体最优:使用摄像头最少;

​ 接下来就是代码实现,难点也在这里,此处引入状态值来方便对节点的统一处理:

​ 节点分为两大类状态:有摄像头和没摄像头,没摄像头又分为节点被覆盖和节点无覆盖;

​ 即三类状态分别用三个数字表示:

​ 0:该节点无覆盖
​ 1:本节点有摄像头
​ 2:本节点有覆盖

​ 接下来还有一个问题,那就是当遍历当空节点的时候,空节点的状态是什么?

​ 分析不难得出,空节点不能是无覆盖的状态,这样叶子节点就要放摄像头了;

​ 空节点也不能是有摄像头的状态,这样叶子节点的父节点就没有必要放摄像头了;

​ 所以空节点一定是有覆盖的情况,其余两种状态均不满足贪心条件;

​ 按照上述图解,可有代码:

	if(left == 2 && right == 2)	return 0;
	if(left == 0 || right == 0)	return 1;
	if(left == 1 || right == 1)	return 2;

​ 代码虽然简单,涉及的情况实际不少;

​ 考虑第一行,不用考虑了好像:);

​ 考虑第二行代码,有:

  • left == 0 && right == 0 左右节点无覆盖
  • left == 1 && right == 0 左节点有摄像头,右节点无覆盖
  • left == 0 && right == 1 左节点有无覆盖,右节点摄像头
  • left == 0 && right == 2 左节点无覆盖,右节点覆盖
  • left == 2 && right == 0 左节点覆盖,右节点无覆盖

​ 这五种情况都要放摄像头,因为按照题意,只要叶子节点存在没有被覆盖的情况,父节点应该放一个摄像头

​ 考虑第三行代码,其实面临的情况已经很少了,因为[0,1]的情况已经讨论过了;

​ 所以面临的情况只有三种:

  • left == 1 && right == 2 左节点有摄像头,右节点有覆盖
  • left == 2 && right == 1 左节点有覆盖,右节点有摄像头
  • left == 1 && right == 1 左右节点都有摄像头

​ 按照题意,只要子节点存在摄像头,父节点一定被覆盖

​ 其实还有一种隐藏情况,这是选择贪心思路时遗留下来的,就是关于头节点的处理;

​ 此时根节点还需要添加一个摄像头,因为需要确保所有节点均覆盖;

​ ok,分析完毕,可以写代码了:

	int res;
	int traversal(TreeNode* cur){
        if(cur == NULL)	return 2;//空节点return 2
        int left = traversal(cur->left);//左
        int right = traversal(cur->right);//右
        //中
        if(left == 2 && right == 2)	return 0;
        else if(left == 0 || right == 0){
            res++;
            return 1;
        }
		else return 2;
    }
	int minCameraCover(TreeNode* root){
        res = 0;
        if(traversal(root) == 0)	res++;
        return res;
    }

贪心无套路,也没有固定框架,贪心算法最重要的思路就是局部最优推全局最优,不严格的证明做题足矣

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值