目录
1. T738:单调递增的数字
2. T714:买卖股票的最佳时机含手续费【动规暂搁】
2.1 思路
2.2 代码实现
3. T968:监控二叉树
思路
3.1 思路
3.1.1 二叉树遍历
3.1.2 处理逻辑
3.2 代码实现
T738:当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。
给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增 。
提示:
0 <= n <= 109
S:这题开始不再排序~
我自己一度有2个疑问 -> 悟了:
当需要 -- 的时候,由于只 -- 1次,所以前一位 -- 完以后也并不能保证 -- 完之后会局部单调递增;
而是要在后一位赋值为9以后才能实现局部单调递增;
实际上 -- 为的,除了下一次的比较,主要是最后一次 --,也就是首位数字 --
如果当前要 -- 的位置数值已经是0,再 -- 会怎么样?(本题中实际上不可能发生,纯粹就是好奇)
C++:
int monotoneIncreasingDigits(int n) {
if (n <= 9) return n;
string numStr = to_string(n);
// int flag = 10;
// flag用来标记赋值9从哪里开始
// 设置为这个默认值,为了防止第二个for循环在flag没有被赋值的情况下执行
int flag = numStr.size();
for (int i = numStr.size() - 1; i > 0; --i) {
if (numStr[i - 1] > numStr[i]) {
flag = i;
numStr[i - 1]--;// 1、为的是最后一下;2、好奇:如果是0?
}
}
for (int i = numStr.size() - 1; i >= flag; --i) {
numStr[i] = '9';
}
return stoi(numStr);
}
Java:
public int monotoneIncreasingDigits(int n) {
char[] numStr = String.valueOf(n).toCharArray();
int flag = numStr.length;
for (int i = numStr.length - 1; i > 0; --i) {
if (numStr[i - 1] > numStr[i]) {
flag = i;
numStr[i - 1]--;
}
}
for (int i = numStr.length - 1; i >= flag; --i) {
numStr[i] = '9';
}
return Integer.parseInt(String.valueOf(numStr));
}
T714:给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
提示:
1 <= prices.length <= 5 * 104
1 <= prices[i] < 5 * 104
0 <= fee < 5 * 104
S:跟前面的T122:买卖股票的最佳时机Ⅱ相比,本题的唯一不同就在于每次买卖都要交手续费,也就是说如果赚的钱还交不起手续费,那就别卖,或者干脆刚开始就别买(这样更好)。
比如下例,在8卖6买,或者一直到9才卖都是一样的收益为6,区别仅在于交了几次手续费
prices : [1,3,2,8,6,9],fee = 2
如果使用贪心策略,就是最低值买,最高值(如果算上手续费还盈利)就卖。
此时无非就是要找到两个点:买入日期和卖出日期。
买入日期:其实很好想,遇到更低点就记录一下。
卖出日期:这个就不好算了,但也没有必要算出准确的卖出日期,只要当前价格大于(最低价格+手续费),就可以收获利润,至于准确的卖出日期,就是连续收获利润区间里的最后一天(并不需要计算是具体哪一天)。
在做收获利润操作的时候其实有三种情况:
情况一:收获利润的这一天,并不是收获利润区间里的最后一天(不是真正的卖出,相当于持有股票),所以,后面要继续收获利润。
情况二:前一天是收获利润区间里的最后一天(相当于真正的卖出了),今天要重新记录最小价格了。
情况三:不作操作,保持原有状态(买入,卖出,不买不卖)
C++:
int maxProfit(vector& prices, int fee) {
int res = 0;
int minPrice = INT_MAX;
for (int i = 0; i < prices.size(); ++i) {
// 情况二:结合情况一的操作,就可以相当于买入
minPrice = min(prices[i], minPrice);
// 情况三:保持原有状态
if (prices[i] > minPrice && prices[i] <= minPrice + fee) {
continue;// 已经买了就继续拿着,没买就别买,因为此时买则不便宜,卖则亏本
}
// 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出(如果下次循环没有符合情况二,就当这次没卖)
if (prices[i] > minPrice + fee) {
res += prices[i] - fee - minPrice;
minPrice = prices[i] - fee;// 情况一:核心理解行
}
}
return res;
}
Java:
public int maxProfit(int[] prices, int fee) {
if (prices.length == 1) return 0;
int res = 0;
int minPrice = prices[0];
for (int i = 1; i < prices.length; ++i) {
minPrice = prices[i] < minPrice ? prices[i] : minPrice;
if (prices[i] > minPrice + fee) {
res += prices[i] - fee - minPrice;
minPrice = prices[i] - fee;
}
}
return res;
}
T968:给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
提示:
给定树的节点数的范围是 [1, 1000]。
每个节点的值都是 0。
S:有一阵没有这么懵了,说实话就算看完题解并且理解,还是自认光凭自己想不到这份上。。。
首先,要遍历二叉树,而且是从叶子节点开始遍历,因为头结点放不放摄像头也就省下一个摄像头,而叶子节点放不放摄像头省下了的摄像头数量是指数阶别的。
所以我们要从下往上看,
局部最优:让叶子节点的父节点安摄像头,所用摄像头最少,
整体最优:全部摄像头数量所用最少!
而从下往上遍历,自然是用后序遍历~
每个节点自身可能有如下三种状态:
该节点无覆盖
本节点有摄像头
本节点有覆盖
分别用三个数字来表示:
0:该节点无覆盖
1:本节点有摄像头
2:本节点有覆盖
其实我个人更喜欢这样表示【见Java版】:
0:该节点无覆盖
1:本节点有覆盖
2:本节点有摄像头
每个结点可能符合如下四类情况之一:
情况1:左右节点都有覆盖
左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态了。【从下往上】
情况2:左右节点至少有一个无覆盖的情况
如果是以下情况,则中间节点(父节点)应该放摄像头:
left == 0 && right == 0 左右节点无覆盖;
left == 1 && right == 0 左节点有摄像头,右节点无覆盖;
left == 0 && right == 1 左节点有无覆盖,右节点摄像头;
left == 0 && right == 2 左节点无覆盖,右节点覆盖;
left == 2 && right == 0 左节点覆盖,右节点无覆盖
情况3:左右节点至少有一个有摄像头
如果是以下情况,其实就是 左右孩子节点有一个有摄像头了,那么其父节点应该是2(覆盖状态)
left == 1 && right == 2 左节点有摄像头,右节点有覆盖;
left == 2 && right == 1 左节点有覆盖,右节点有摄像头;
left == 1 && right == 1 左右节点都有摄像头
情况4:头结点没有覆盖
以上都处理完了,递归结束之后,还有一种可能,就是头结点还处于无覆盖的情况
C++:
int minCameraCover(TreeNode* root) {
res = 0;
if (!traversal(root)) ++res;// 情况四
return res;
}
private:
int res;
int traversal(TreeNode* node) {
if (!node) return 2;// 空节点,该节点有覆盖
int left = traversal(node->left);
int right = traversal(node->right);
if (left == 2 && right == 2) {// 情况一
return 0;
} else if (!left || !right) {// 情况二
++res;
return 1;
} else if (left == 1 || right == 1) {// 情况三
return 2;
}
// 这个 return -1 逻辑不会走到这里。
return -1;
}
Java:用了自己修改过的节点状态定义方式,其实还是一样的
private int res = 0;
public int minCameraCover(TreeNode root) {
if (traversal(root) == 0) ++res;
return res;
}
private int traversal(TreeNode node) {
if (node == null) return 1;
int left = traversal(node.left);
int right = traversal(node.right);
if (left == 1 && right == 1) {
return 0;
} else if (left == 0 || right == 0) {
++res;
return 2;
} else if (left == 2 || right == 2) {
return 1;
}
return -1;
}