第三十七天| 第八章 贪心算法part06 738. 单调递增的数字 968. 监控二叉树
一、738. 单调递增的数字
-
题目链接:https://leetcode.cn/problems/monotone-increasing-digits/
-
题目介绍:
-
当且仅当每个相邻位数上的数字
x
和y
满足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,只有两种情况,一种是它是null,另一种就是:它下面有一个摄像头
- 解释如下:
- 情况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;
}
}
- 代码很简洁,思路要理清