Leetcode 738.单调递增的数字
题目链接:738 单调递增的数字
题干:当且仅当每个相邻位数上的数字
x
和y
满足x <= y
时,我们称这个整数是单调递增的。给定一个整数n
,返回 小于或等于n
的最大数字,且数字呈 单调递增 。
思考:贪心法。首先可以将数字n转变为字符串num来处理。接着一旦出现num[i - 1] > num[i]的情况(非单调递增),首先想让num[i - 1]--,然后num[i]给为9。最后要确认遍历的顺序:从前往后遍历还是从后往前遍历。
从前向后遍历的话,遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]减一,但此时如果strNum[i - 1]减一了,可能又小于strNum[i - 2]。举例数字:332,从前向后遍历的话,那么就把变成了329,此时2又小于了第一位的3了,真正的结果应该是299。而从后向前遍历332的数值变化为:332 -> 329 -> 299。因此选取从后向前遍历。
最后代码实现的时候,存在一些技巧,可以用一个flag来标记从哪里开始赋值9。
代码:
class Solution {
public:
int monotoneIncreasingDigits(int n) {
string num = to_string(n);
int flag = num.size(); //记录修改为9的起始位置
for (int i = num.size() - 1; i > 0; i--) {
if (num[i] < num[i - 1]) {
flag = i; //更新修改位置
num[i - 1]--;
}
}
for (int i = flag; i < num.size(); i++) //修改标记位置之后的数值
num[i] = '9';
return atoi(num.c_str());
}
};
Leetcode 968.监控二叉树
题目链接:968 监控二叉树
题干:给定一个二叉树,我们在树的节点上安装摄像头。节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。计算监控树的所有节点所需的最小摄像头数量。
思考:贪心法+递归处理。本题的难点在于贪心思路。
-
整体思路
由题目给的案例可以观察出摄像头不放置在叶子节点。摄像头可以覆盖上中下三层,如果把摄像头放在叶子节点上,就浪费的一层的覆盖。 所以把摄像头放在叶子节点的父节点位置,才能充分利用摄像头的覆盖面积。
又因为头结点放不放摄像头也就省下一个摄像头, 叶子节点放不放摄像头省下了的摄像头数量是指数阶别的。所以我们要从下往上看,局部最优:让叶子节点的父节点安摄像头,所用摄像头最少,整体最优:全部摄像头数量所用最少!
此时,大体思路就是从低到上,先给叶子节点父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点。
-
处理难点
-
确定遍历顺序
由于要从叶子节点开始处理,因此我们选取后序遍历,这样在回溯的过程中处理从下到上推理。
后序遍历代码模板:
int traversal(TreeNode* cur) {
if (终止条件) return ;
int left = traversal(cur->left); // 左
int right = traversal(cur->right); // 右
逻辑处理 // 中
return ;
}
-
如何隔两个节点放一个摄像头
先来确定每个节点可能有几种状态:
有如下三种:
- 该节点无覆盖(用0表示)
- 本节点有摄像头(用1表示)
- 本节点有覆盖(用2表示)
考虑终止条件:遇到空节点应该返回什么状态
首先空节点不能是无覆盖的状态,这样叶子节点就要放摄像头。其次空节点也不能是有摄像头的状态,这样叶子节点的父节点就没有必要放摄像头。所以空节点的状态只能是有覆盖,这样就可以在叶子节点的父节点放摄像头。
考虑单层递归处理逻辑:节点的四种情况
- 情况1:左右节点至少有一个无覆盖的情况
如果是以下情况,则中间节点(父节点)应该放摄像头:
- left == 0 && right == 0 左右节点无覆盖
- left == 1 && right == 0 左节点有摄像头,右节点无覆盖
- left == 0 && right == 1 左节点有无覆盖,右节点摄像头
- left == 0 && right == 2 左节点无覆盖,右节点覆盖
- left == 2 && right == 0 左节点覆盖,右节点无覆盖
此时摄像头的数量要加一,返回状态为1
- 情况2:左右节点至少有一个有摄像头
如果是以下情况,则中间节点(父节点)应该是覆盖的状态:
- left == 1 && right == 2 左节点有摄像头,右节点有覆盖
- left == 2 && right == 1 左节点有覆盖,右节点有摄像头
- left == 1 && right == 1 左右节点都有摄像头
此时返回状态为2
- 情况3:左右节点都有覆盖
以上的剩余情况:左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态,返回状态为0。
- 情况4:头结点没有覆盖
以上都处理完了,递归结束之后,可能头结点 还有一个无覆盖的情况,如图:
所以递归结束之后,还要判断根节点,如果没有覆盖,result++
代码:
class Solution {
public:
int result = 0; //记录摄像头个数
//0表示无覆盖, 1表示有摄像头, 2表示有覆盖
int traversal(TreeNode* node) {
if (!node) return 2; //空节点显示为有覆盖
int left = traversal(node->left); //左
int right = traversal(node->right); //右
if (left == 0 || right == 0) { //左右孩子节点存在未覆盖的情况
result++;
return 1;
} else if (left == 1 || right == 1) //左右孩子存在摄像头
return 2;
else //左右孩子均为有覆盖
return 0;
}
int minCameraCover(TreeNode* root) {
result = 0;
if (traversal(root) == 0) //根root未覆盖
result++;
return result;
}
};
贪心专题的总结:
- 贪心的本质是选择每一阶段的局部最优,从而达到全局最优
- 两个维度权衡问题的精髓在于排序,难点在于排序顺序,优先处理哪一个维度。
- 贪心无套路,也没有框架之类的,需要多看多练培养感觉才能想到贪心的思路。考虑局部最优时要想想是否存在反例