单调递增的数字
给定一个非负整数 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:
- 输入:[0,0,null,0,0]
- 输出:1
- 解释:如图所示,一台摄像头足以监控所有节点。
示例 2:
- 输入:[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;
}
贪心无套路,也没有固定框架,贪心算法最重要的思路就是局部最优推全局最优,不严格的证明做题足矣。