目录:
解题及思路学习
738.单调递增的数字
https://leetcode.cn/problems/monotone-increasing-digits/
当且仅当每个相邻位数上的数字 x
和 y
满足 x <= y
时,我们称这个整数是单调递增的。
给定一个整数 n
,返回 小于或等于 n
的最大数字,且数字呈 单调递增 。
示例 1:
输入: n = 10
输出: 9
思考:根据这个数字,可以确定其满足条件的最大整数是几位数的,例如当n为除了10以外的两位数,那么该整数也是两位数的。确定了位数之后再确认左边第一个数,先从等于n最左边找齐,再从n-1开始搜索。
随想录:如果n本身单调递增,那就是该数值。如果n本身不递增,则将第一个数减一,然后后面的数取9.
例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]–,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。
代码:
class Solution {
public:
int monotoneIncreasingDigits(int N) {
string strNum = to_string(N);
// flag用来标记赋值9从哪里开始
// 设置为这个默认值,为了防止第二个for循环在flag没有被赋值的情况下执行
int flag = strNum.size();
for (int i = strNum.size() - 1; i > 0; i--) {
if (strNum[i - 1] > strNum[i] ) {
flag = i;
strNum[i - 1]--;
}
}
for (int i = flag; i < strNum.size(); i++) {
strNum[i] = '9';
}
return stoi(strNum);
}
};
- 时间复杂度:O(n),n 为数字长度
- 空间复杂度:O(n),需要一个字符串,转化为字符串操作更方便
总结:我思路想到了一半,这个flag非常巧妙。 这道题里面的 to_string() 和 stoi() 接口也很不错。
968.监控二叉树
https://leetcode.cn/problems/binary-tree-cameras/
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
示例 1:
!https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/29/bst_cameras_01.png
输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。
思考:没啥好的思路
随想录:**题目示例中的摄像头都没有放在叶子节点上!**这是很重要的一个线索,摄像头可以覆盖上中下三层,如果把摄像头放在叶子节点上,就浪费的一层的覆盖。所以把摄像头放在叶子节点的父节点位置,才能充分利用摄像头的覆盖面积。所以我们要从下往上看,局部最优:让叶子节点的父节点安摄像头,所用摄像头最少,整体最优:全部摄像头数量所用最少!
此时,大体思路就是从低到上,先给叶子节点父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点。
此时这道题目还有两个难点:
- 二叉树的遍历
- 如何隔两个节点放一个摄像
每个节点可能有几种状态:
- 0:该节点无覆盖
- 1:本节点有摄像头
- 2:本节点有覆盖
空节点的状态只能是有覆盖,这样就可以在叶子节点的父节点放摄像头了
单层逻辑处理。主要有如下四类情况:
- 情况1:左右节点都有覆盖,那么此时中间节点应该就是无覆盖的状态了。
- 情况2:左右节点至少有一个无覆盖的情况,则中间节点(父节点)应该放摄像头:
- 情况3:左右节点至少有一个有摄像头,那么其父节点就应该是2(覆盖的状态)
- 情况4:头结点没有覆盖,所以递归结束之后,还要判断根节点,如果没有覆盖,result++
// 版本一
class Solution {
private:
int result;
int traversal(TreeNode* cur) {
// 空节点,该节点有覆盖
if (cur == NULL) return 2;
int left = traversal(cur->left); // 左
int right = traversal(cur->right); // 右
// 情况1
// 左右节点都有覆盖
if (left == 2 && right == 2) return 0;
// 情况2
// left == 0 && right == 0 左右节点无覆盖
// left == 1 && right == 0 左节点有摄像头,右节点无覆盖
// left == 0 && right == 1 左节点有无覆盖,右节点摄像头
// left == 0 && right == 2 左节点无覆盖,右节点覆盖
// left == 2 && right == 0 左节点覆盖,右节点无覆盖
if (left == 0 || right == 0) {
result++;
return 1;
}
// 情况3
// left == 1 && right == 2 左节点有摄像头,右节点有覆盖
// left == 2 && right == 1 左节点有覆盖,右节点有摄像头
// left == 1 && right == 1 左右节点都有摄像头
// 其他情况前段代码均已覆盖
if (left == 1 || right == 1) return 2;
// 以上代码我没有使用else,主要是为了把各个分支条件展现出来,这样代码有助于读者理解
// 这个 return -1 逻辑不会走到这里。
return -1;
}
public:
int minCameraCover(TreeNode* root) {
result = 0;
// 情况4
if (traversal(root) == 0) { // root 无覆盖
result++;
}
return result;
}
};
精简版
// 版本二
class Solution {
private:
int result;
int traversal(TreeNode* cur) {
if (cur == NULL) 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) {
result++;
return 1;
} else return 2;
}
public:
int minCameraCover(TreeNode* root) {
result = 0;
if (traversal(root) == 0) { // root 无覆盖
result++;
}
return result;
}
};
- 时间复杂度: O(n),需要遍历二叉树上的每个节点
- 空间复杂度: O(n)
本题的难点首先是要想到贪心的思路,然后就是遍历和状态推导。
在二叉树上进行状态推导,其实难度就上了一个台阶了,需要对二叉树的操作非常娴熟。
知识点记录
知识点
https://programmercarl.com/贪心算法总结篇.html#贪心理论基础
个人反思
如果找到局部最优,然后推出整体最优,那么就是贪心
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1jwGpYmi-1688265061574)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/716ff013-b61d-4729-b9a8-eb99ecf3ac44/Untitled.png)]