738.单调递增的数字
1.暴力法:当前元素递减,每次减一都判断一下是否满足单调递增数字条件。不出意外的超时了,只通过了三分之二的测试用例。但考虑到是自己写的,还是贴一下代码。
class Solution {
bool isIncrease(int n) {
vector<int> result;
while (n) {
result.push_back(n % 10);
n = n / 10;
}
for (int i = 0; i < result.size() - 1; i++) {
if (result[i] < result[i + 1])
return false;
}
return true;
}
public:
int monotoneIncreasingDigits(int n) {
while (n) {
if (isIncrease(n))
break;
else
n--;
}
return n;
}
};
2.正经解法:贪心。
例如:98,一旦出现s[i - 1] > s[i]的情况(非单调递增),首先想让st[i - 1]--,然后s[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。这一点如果想清楚了,这道题就好办了。此时是从前向后遍历还是从后向前遍历呢?从前向后遍历的话,遇到s[i - 1] > s[i]的情况,让s[i - 1]减一,但此时如果s[i - 1]减一了,可能又小于s[i - 2]。这么说有点抽象,举个例子,数字:332,从前向后遍历的话,那么就把变成了329,此时2又小于了第一位的3了,真正的结果应该是299。那么从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299。(从后向前遍历,找到最后一个不满足单调递增的位置,用flag标记。将flag前一位元素减一,flag及之后的元素全部变为9)
class Solution {
public:
int monotoneIncreasingDigits(int n) {
string s = to_string(n); //整数转字符串
int flag = s.size(); // flag标记从哪开始赋值9
for (int i = s.size() - 1; i > 0; i--) {
if (s[i - 1] > s[i]) {
s[i - 1]--; //更新元素,防止之后误判
flag = i; //更新标记位置
}
}
for (int j = flag; j < s.size(); j++) {
s[j] = '9'; //标记及以后位置全部赋值为9
}
return stoi(s); //字符串转整数
}
};
968.监控二叉树
1.贪心:随想录版。局部最优:让叶子节点的父节点安摄像头,所用摄像头最少,整体最优:全部摄像头数量所用最少!二叉树中从低向上推导,采用后序遍历。注意三种状态的分析0:该节点无覆盖1:本节点有摄像头2:本节点有覆盖。(笔者也只是勉强看懂题解,暂给不出更犀利的见解,详情还请移步随想录网站)
/**
* 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) {
// 空节点,该节点有覆盖
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;
}
};
2.官解:动态规划。代码十分简洁,核心是罗列出需要维护的状态并分析状态转移转移方程。这实际上十分困难,是当之无愧的hard题。(注:个人水平有限,详情请看官方网站。)
/**
* 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) {}
* };
*/
struct Status {
int a, b, c;
};
class Solution {
public:
Status dfs(TreeNode* root) {
if (!root) {
return {INT_MAX / 2, 0, 0};
}
auto [la, lb, lc] = dfs(root->left);
auto [ra, rb, rc] = dfs(root->right);
int a = lc + rc + 1;
int b = min(a, min(la + rb, ra + lb));
int c = min(a, lb + rb);
return {a, b, c};
}
int minCameraCover(TreeNode* root) {
auto [a, b, c] = dfs(root);
return b;
}
};
今日总结:贪心+二叉树,闹麻了。