738.单调递增的数字
贪心思路:从个位向前看,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先让strNum[i - 1]--,然后strNum[i]及其之后的数字变为9
我们从后向前遍历数字,按照思路进行处理
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; // 位置flag及其之后的数字变为9
strNum[i - 1]--;
}
}
for (int i = flag; i < strNum.size(); i++) {
strNum[i] = '9';
}
return stoi(strNum);
}
};
注意 to_string 和 stoi 的运用,需要#include<cstring>
968.监控二叉树
看到二叉树,我们总会想到递归三部曲或回溯
本题就用递归三部曲吧,需要结合贪心的思想
首先需要明确递归函数的功能: 递归函数功能是传入一个树的节点,返回其应该被置为的状态
这里所谓的节点的状态有三种
- 节点安装了摄像头
- 节点无摄像头但被别的摄像头覆盖
- 节点无摄像头且未被别的摄像头覆盖
我们分别有三个数字来表示:
- 0:该节点无覆盖
- 1:该节点有摄像头
- 2:该节点无摄像头但有被覆盖
然后进入三部曲
1、确定参数和返回值
根据递归函数作用得知:参数为一个节点,返回值为 int 表示其应该被置的状态
int traversal(TreeNode* cur)
2、确定终止条件
遇到空节点,应该返回其应该被置为的状态。那么问题来了,空节点究竟是哪一种状态呢? 空节点表示无覆盖? 表示有摄像头?还是有覆盖呢?
回归我们的贪心思想:为了让摄像头数量最少,我们要从叶子节点往根节点逐步设置节点状态,尽量让叶子节点的父节点安装摄像头,这样才能摄像头的数量最少。那么空节点不能是有摄像头的状态,这样叶子节就没有必要用摄像头去覆盖了,而是可以把摄像头放在叶子节点的爷爷节点上,会发生漏覆盖;空节点也不能是未覆盖的状态,这样叶子节点就要放摄像头了,这样摄像头可能会放多了
所以空节点的状态只能是有覆盖,这样就可以在叶子节点的父节点放摄像头了
// 空节点,该节点有覆盖
if (cur == NULL) return 2;
3、确定单层递归的逻辑
我们从叶子节点往上依次设置状态。当前节点如何被设置取决于其左右子节点被设置为的状态,左右子节点的状态设置情况可以通过调用递归函数本身获取
代码如下,包含了三种情况的设置
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
int result; // 统计摄像头个数
int traversal(TreeNode* cur) {
//0:该节点无覆盖
//1:本节点有摄像头
//2:本节点有覆盖
// 空节点,该节点有覆盖
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;
if (traversal(root) == 0) { // root 无覆盖,需要在root的地方加一个摄像头
result++;
}
return result;
}
};
需要注意,root 如果最后被置的状态为0,表明节点 root 还未被覆盖,需要在 root 处增加一个摄像头
回顾总结
贪心算法章节告一段落
贪心算法确实没啥规律,唯一稍微有点儿规律的就是重叠区间问题中的排序、遍历、判断重叠。当然只是相对有规律,具体的处理过程还是要看题目描述的,也具有很大差异性
另外提一下,很多贪心算法都需要排序