贪心算法
贪心的本质是:选择每一阶段的局部最优解,从而达到全局最优。
贪心没有固定的套路,比较好的策略是举反例,想不到反例,就试试贪心。
分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i
,都有一个胃口值 g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j
,都有一个尺寸 s[j]
。如果 s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
贪心 + 双指针:
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int res = 0;
for (int i = 0, j = 0; i < s.length && j < g.length; i++) {
if (s[i] >= g[j]) {
// 饼干能喂饱孩子
res++;
j++; // 下一个孩子
}
}
return res;
}
优化以上代码:可以发现 res 和 j 其实是一样的,只需要一个变量即可
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int res = 0;
for (int i = 0; i < s.length && res < g.length; i++)
if (g[res] <= s[i]) res++;
return res;
}
摆动序列*
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
- 例如,
[1, 7, 4, 9, 2, 5]
是一个 摆动序列 ,因为差值(6, -3, 5, -7, 3)
是正负交替出现的。 - 相反,
[1, 4, 7, 2, 5]
和[1, 7, 4, 5, 5]
不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组nums
,返回nums
中作为 摆动序列 的 最长子序列的长度 。
输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。
模拟:
public int wiggleMaxLength(int[] nums) {
if (nums.length == 1) return 1;
int res = 1;
Boolean flag = null; // 控制方向
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i - 1]) continue; // 0
if (nums[i] - nums[i - 1] > 0) {
// 正值
if (flag != null && flag) continue;
flag = true;
} else {
// 负值
if (flag != null && !flag) continue;
flag = false;
}
res++;
}
return res;
}
动态规划 一维:
public int wiggleMaxLength(int[] nums) {
int n = nums.length;
if (n == 1) return 1;
int[] up = new int[n]; // 以 i 升序的最长子序列的长度
int[] down = new int[n]; // 以 i 降序的最长子序列的长度
up[0] = down[0] = 1;
for (int i = 1; i < n; i++) {
if (nums[i] > nums[i - 1]) {
// 升序
up[i] = Math.max(up[i - 1], down[i - 1] + 1);
down[i] = down[i - 1];
} else if (nums[i] < nums[i - 1]) {
// 降序
up[i] = up[i - 1];
down[i] = Math.max(up[i - 1] + 1, down[i - 1]);
} else {
// 不变
up[i] = up[i - 1];
down[i] = down[i - 1];
}
}
return Math.max(up[n - 1], down[n - 1]);
}
动态规划 二维:
public int wiggleMaxLength(int[] nums) {
int n = nums.length;
if (n <= 1) return n;
// dp[i][0] 表示 nums[0] 到 nums[i] 的最长摆动序列长度, 且 nums[i] < nums[i - 1], 往下
// dp[i][1] 表示 nums[0] 到 nums[i] 的最长摆动序列长度, 且 nums[i] > nums[i - 1], 往上
int[][] dp = new int[n][2];
dp[0][0] = dp[0][1] = 1;
for (int i = 1; i < n; i++) {
if (nums[i] < nums[i - 1]) {
dp[i][0] = dp[i - 1][1] + 1;
dp[i][1] = dp[i - 1][1];
} else if (nums[i] > nums[i - 1]) {
dp[i][0] = dp[i - 1][0];
dp[i][1] = dp[i - 1][0] + 1;
} else {
dp[i][0] = dp[i - 1][0];
dp[i][1] = dp[i - 1][1];
}
}
return Math.max(dp[n - 1][0], dp[n - 1][1]);
}
最大子序和
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
动态规划:
public int maxSubArray(int[] nums) {
// dp[i] 以 i 结尾的最大连续子数组和
int[] dp = new int[nums.length];
int max = dp[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
max = Math.max(max, dp[i]);
}
return max;
}
贪心:
public int maxSubArray(int[] nums) {
int max = nums[0];
int tmp = 0; // 保存临时值
for (int num : nums) {
tmp += num;
if (tmp > max) max = tmp;
if (tmp < 0) tmp = 0;
}
return max;
}
买卖股票的最佳时机
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
总利润为 4 + 3 = 7 。
一维 DP * 2:
public int maxProfit(int[] prices) {
int n = prices.length;
int[] buy = new int[n]; // buy[i] 第 i 天买能获得的最大利益
int[] sell = new int[n]; // sell[i] 第 i 天卖能获得的最大利益
buy[0] = -prices[0];
int max = 0;
for (int i = 1; i < n; i++) {
buy[i] = Math.max(buy[i - 1], sell[i - 1] - prices[i]);
sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]);
max = Math.max(max, sell[i]); // 最大利益只会产生在卖的时候
}
return max;
}
二维 DP:
public int maxProfit(int[] prices) {
int n = prices.length;
// dp[i][0] - 第 i 天[买]能获得的最大利益
// dp[i][1] - 第 i 天[卖]能获得的最大利益
int[][] dp = new int[n][2];
dp[0][0] = -prices[0];
dp[0][1] = 0;
int max = 0;
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
max = Math.max(max, dp[i][1]);
}
return max;
}
贪心:今天比昨天大就直接卖出
public int maxProfit(int[] prices) {
int res = 0;
for (int i = 1; i < prices.length; i++)
if (prices[i] > prices[i - 1])
res += prices[i] - prices[i - 1];
return res;
}
跳跃游戏
给定一个非负整数数组 nums
,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
动态规划:
public boolean canJump(int[] nums) {
int[] dp = new int[nums.length]; // dp[i] i 能跳到的最远下标
dp[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
if (dp[i - 1] < i) return false; // 前一个位置无法到达当前位置, 直接结束
dp[i] = Math.max(dp[i - 1], i + nums[i]); // 计算当前位置能跳到的最远位置
}
return true;
}
贪心:
public boolean canJump(int[] nums) {
int cover = 0; // 覆盖范围
for (int i = 0; i <= cover; i++) {
cover = Math.max(cover, i + nums[i]); // 每一步的最大覆盖范围
if (cover >= nums.length - 1) return true; // 能覆盖到终点
}
return false;
}
跳跃游戏 II*
给你一个非负整数数组 nums
,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
DP:
public int jump(int[] nums) {
int n = nums.length;
int[] dp = new int[n]; // dp[i] 跳到 i 位置使用的最少跳跃次数
Arrays.fill(dp, 10001);
dp[0] = 0;
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
if (nums[j] + j >= i)
dp[i] = Math.min(dp[i], dp[j] + 1);
return dp[n - 1];
}
贪心:
public int jump(int[] nums) {
int res = 0;
int curDis = 0; // 当前的跳跃次数下最远能达到的距离
int maxDis = nums[0]; // 当前的跳跃次数下再加一次最远能达到的距离
for (int i = 1; i < nums.length; i++) {
// 能在当前跳跃次数下到达就不会多跳
if (i > curDis) {
// 无法在当前跳跃次数下到达
res++;
curDis = maxDis;
}
// 更新当前次数下再跳一次能到达的最远距离
maxDis = Math.max(maxDis, i + nums[i]);
}
return res;
}
K 次取反后最大化的数组*
给你一个整数数组 nums
和一个整数 k
,按以下方法修改该数组:
- 选择某个下标
i
并将nums[i]
替换为-nums[i]
。
重复这个过程恰好k
次。可以多次选择同一个下标i
。
以这种方式修改数组后,返回数组 可能的最大和 。
优先级队列:
public int largestSumAfterKNegations(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小根堆
for (int n : nums) pq.add(n);
int res = 0;
// 每次只将最小的值取反
while (k-- > 0) pq.add(-pq.poll());
while (!pq.isEmpty()) res += pq.poll();
return res;
}
模拟 + 贪心:
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums);
int idx = 0; // 遍历下标
while (k-- > 0) {
// 当前数 < 0, 取反后和下一位进行比较
if (idx < nums.length - 1 && nums[idx] < 0) {
nums[idx] = -nums[idx]; // 取反
//
if (nums[idx] >= Math.abs(nums[idx + 1])) idx++;
continue;
}
// 当前数 >= 0
nums[idx] = -nums[idx]; // 取反
}
int res = 0;
for (int i = 0; i < nums.length; i++) res += nums[i];
return res;
}
加油站*
在一条环路上有 n
个加油站,其中第 i
个加油站有汽油 gas[i]
升。
你有一辆油箱容量无限的的汽车,从第 i
个加油站开往第 i+1
个加油站需要消耗汽油 cost[i]
升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas
和 cost
,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1
。如果存在解,则 保证 它是 唯一 的。
输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
贪心思想:
- 如果 gas 的总和小于 cost 总和,那么无论从哪里出发,肯定跑不了一圈
- 当前油量的累加一旦小于 0,起始位置至少是 i + 1
public int canCompleteCircuit(int[] gas, int[] cost) {
int totalNum = 0; // 总油量
int curNum = 0; // 当前油量
int start = 0; // 开始位置
for (int i = 0; i < gas.length; i++) {
curNum += gas[i] - cost[i];
totalNum += gas[i] - cost[i];
// 遇到油量不够的请求, 从当前开始重新计算
if (curNum < 0) {
start = i + 1; // 更新起始位置为 i + 1
curNum = 0;
}
}
return totalNum < 0 ? -1 : start;
}
分发糖果*
n
个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1
个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
每个孩子的评分有 2 次比较,左比较和右比较,对于碰到类似这种左右比较或者相邻比较,都可以考虑两次遍历。
需要考虑多个维度情况的题目,尝试分别考虑每个维度,不要想着同时考虑两个维度。
public int candy(int[] ratings) {
int[] candies = new int[ratings.length];
Arrays.fill(candies, 1);
// 从左往右: 只比较右边孩子评分比左边大的情况
for (int i = 1; i < candies.length; i++)
if (ratings[i] > ratings[i - 1])
candies[i] = candies[i - 1] + 1;
// 从右往左: 只比较左边孩子评分比右边大的情况
for (int i = candies.length - 2; i >= 0; i--)
if (ratings[i] > ratings[i + 1])
// 考虑这个维度要在第一个维度的基础上考虑, 即还得维护满足第一个维度的条件
candies[i] = Math.max(candies[i], candies[i + 1] + 1);
// 以上两个维度综合 => 相邻的孩子评分高的可以拿更多的糖果
int res = 0;
for (int i = 0; i < candies.length; i++) res += candies[i];
return res;
}
柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5
美元。顾客排队购买你的产品,(按账单 bills
支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5
美元、10
美元或 20
美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5
美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills
,其中 bills[i]
是第 i
位顾客付的账。如果你能给每位顾客正确找零,返回 true
,否则返回 false
。
输入:bills = [5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
public boolean lemonadeChange(int[] bills) {
int[] money = new int[5];
for (int i = 0; i < bills.length; i++) {
if (bills[i] == 5) money[5]++;
else if (bills[i] == 10) {
if (money[5] < 0) return false;
money[10]++;
money[5]--;
} else if (bills[i] == 20) {
if (money[5] > 0 && money[10] > 0) {
money[5]--;
money[10]--;
} else if (money[5] >= 3) {
money[5] -= 3;
} else return false;
}
}
return true;
}
根据身高重建队列
假设有打乱顺序的一群人站成一个队列,数组 people
表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki]
表示第 i
个人的身高为 hi
,前面 正好 有 ki
个身高大于或等于 hi
的人。
请你重新构造并返回输入数组 people
所表示的队列。返回的队列应该格式化为数组 queue
,其中 queue[j] = [hj, kj]
是队列中第 j
个人的属性(queue[0]
是排在队列前面的人)。
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
public int[][] reconstructQueue(int[][] people) {
// 先按身高从高到低排序, 身高一样再按 k 从低到高排序
Arrays.sort(people, (a, b) -> {
if (a[0] == b[0]) return a[1] - b[1];
return b[0] - a[0];
});
LinkedList<int[]> q = new LinkedList<>();
for (int[] p : people) q.add(p[1], p); // 往指定位置插入元素
return q.toArray(new int[people.length][]);
}
----- 区间问题 -----
用最少数量的箭引爆气球*
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points
,其中points[i] = [x_start, x_end]
表示水平直径在 x_start
和 x_end
之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x
处射出一支箭,若有一个气球的直径的开始和结束坐标为 x_start
,x_end
, 且满足 x_start ≤ x ≤ x_end
,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points
,返回引爆所有气球所必须射出的 最小 弓箭数 。
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
- 在 x = 6 处射出箭,击破气球 [2,8] 和 [1,6]。
- 在 x = 11 处发射箭,击破气球 [10,16] 和 [7,12]。
贪心:这题本质上是找最大重叠区间
- 以左边界升序排序
- 重叠时,上一气球和当前气球视为一个区间,更新该区间的最小右边界
- 不重叠时,上一气球和当前气球为两个不同的区间,箭的数量 + 1,最小右边界更新为新区间的
public int findMinArrowShots(int[][] points) {
// 以左边界升序排序
Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));
int res = 1; // 至少需要一支箭
int end = points[0][1]; // 折叠气球的最小右边界
for (int i = 1; i < points.length; i++) {
// 当前气球的左边界 > 最小右边界, 说明不重叠, 箭 + 1
if (points[i][0] > end) {
res++;
end = points[i][1];
}
// 当前气球的左边界 <= 最小右边界, 说明重叠, 更新最小右边界
else end = Math.min(end, points[i][1]);
}
return res;
}
无重叠区间*
给定一个区间的集合 intervals
,其中 intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
贪心:
- 以右边界升序排序
- 计算最多能组成的不重叠的区间个数,然后用区间总个数减去不重叠区间的个数
public int eraseOverlapIntervals(int[][] intervals) {
// 以右边界升序排序
Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));
int cnt = 1; // 不重叠的区间个数
int end = intervals[0][1]; //
for (int i = 0; i < intervals.length; i++) {
// 重叠
if (intervals[i][0] < end) continue;
// 未重叠
end = intervals[i][1];
cnt++;
}
return intervals.length - cnt;
}
划分字母区间
字符串 S
由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
哈希 + 贪心:
- 记录每个小写字母 最后出现的位置
- 遍历时更新区间能到达的最远位置,如果当前遍历位置即为最远,则找到一个划分
public List<Integer> partitionLabels(String s) {
List<Integer> res = new ArrayList<>();
int[] idx = new int[128]; // 记录每个小写字母最后出现的位置
for (int i = 0; i < s.length(); i++) idx[s.charAt(i)] = i;
int start = 0, end = 0; // 当前区间的开始与结束位置
for (int i = 0; i < s.length(); i++) {
end = Math.max(end, idx[s.charAt(i)]); // 更新当前区间的能到的最远距离
if (end == i) {
// 到达当前区间能到的最远距离, 算是找到一个划分
res.add(end - start + 1);
start = end + 1; // 当前区间变为一个新的区间
}
}
return res;
}
合并区间
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
遍历时判断当前区间能否和上一个区间合并:
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
List<int[]> list = new ArrayList<>();
// 每个区间的 起点 和 终点
int start = intervals[0][0], end = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= end) {
// 当前区间可以和上一个区间合并
end = Math.max(end, intervals[i][1]);
} else {
// 当前区间无法和上一个区间合并
list.add(new int[] {
start, end }); // 添加当前区间
start = intervals[i][0]; // 更新起点
end = intervals[i][1]; // 更新终点
}
}
list.add(new int[] {
start, end }); // 最后一个区间
return list.toArray(new int[list.size()][]);
}
----------
单调递增的数字
思路:从右往左扫描,若当前数字比其左边一位小,则把左边一位数字减1,并将该位及其右边所有位改成9
public int monotoneIncreasingDigits(int n) {
char[] cs = String.valueOf(n).toCharArray();
// 从右往左扫描, 若当前数字比其左边一位小
//
int start = cs.length; // 标记哪一位开始要变成9
for (int i = cs.length - 1; i >= 1; i--) {
if (cs[i] < cs[i - 1]) {
cs[i - 1]--;
start = i;
}
}
for (int i = start; i < cs.length; i++) cs[i] = '9';
return Integer.parseInt(new String(cs));
}
买卖股票的最佳时机含手续费
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
int[] sell = new int[n]; // 第 i 天卖股票的最高利润
int[] buy = new int[n]; // 第 i 天买股票的最高利润
buy[0] = -prices[0];
for (int i = 1; i < n; i++) {
// 今天卖股票的最高利润: [卖] 或 [不卖] 两种行为做比较
// [卖]: 之前买的最高利润 + 今天卖的钱 - 手续费
// [不卖]: 之前卖过的最高利润
sell[i] = Math.max(buy[i - 1] + prices[i] - fee, sell[i - 1]);
// 今天买股票的最高利润: [买] 或 [不买] 两种行为做比较
// [买]: 之前卖的最高利润 - 今天买的钱
// [不买]: 之前买过的最高利润
buy[i] = Math.max(sell[i - 1] - prices[i], buy[i - 1]);
}
return sell[n - 1];
}
监控二叉树
此题先跳过。