738.单调递增的数字
当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是 单调递增 的。给定一个整数 n,返回 小于或等于 n 的最大数字,且数字呈 单调递增。贪心策略的一般通解,局部最优则全局最优。我们思考如何确定一个单调递增的整数,需要确保其每一对相邻的数字都满足后者大于等于前者。
当 n[i - 1] > n[i] 时,不符合单调递增数字的要求,此时我们要将 i - 1 位置的数字减 1,再将后面的数字置 9,比如 153, 5 比 3 大,此时我们将 153 变为 149,确保各个数位之间单调递增的同时,还要确保该数字为最大。
class Solution {
public:
int monotoneIncreasingDigits(int n) {
string str = to_string(n);
int flag = str.size();
for (int i = str.size() - 1; i > 0; --i) {
if (str[i] < str[i - 1]) {
--str[i - 1];
flag = i;
}
}
for (int i = flag; i < str.size(); ++i) {
str[i] = '9';
}
return stoi(str);
}
};
为了方便访问和改变数字,将整数转换为字符串,这样可以通过下标随机访问各个数位,并且可以通过自减修改数位的数字。从后向前遍历,从数字的低位向高位遍历,这样低位发生变化,不影响高位,如果从前向后遍历,数字的高位发生变化,会影响低位,进而影响结果。stoi 函数将字符串转换为整数,并且能够将字符串开头的 0 字符去除。
968.监控二叉树
如果想要在全局范围内确保摄像头的数量最少,应该在局部确保每个摄像头监控到足够多的节点。每个节点的监控情况如下(不包含父节点和其本身,因为一个节点总能监视到这两种节点):监控 2 个节点(左右子节点),监控 1 个节点(左 或 右节点),监控 0 个节点(叶子节点)。我们应该尽力避免在叶子节点放置摄像头,保证摄像头都放置在拥有叶子节点的节点上。因此如何判断一个节点有没有左右子树?那一定是后序遍历,先访问其左右子树,才能知道有没有左右子树。确定下使用后序遍历。我们还需要分类状态。
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;
}
};
这里直接引用代码随想录里Carl哥的分类代码(太繁琐,不想敲字)。本题的难点在于:分析使用后序遍历以及分类情况。