文章目录
- 想说的话
- [121. 买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/)
- [122. 买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
- [123. 买卖股票的最佳时机 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/)
- [188. 买卖股票的最佳时机 IV](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/)
- [309. 最佳买卖股票时机含冷冻期](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
- [714. 买卖股票的最佳时机含手续费](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)
- [剑指 Offer 63. 股票的最大利润](https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/)
想说的话
-
这类型的题,与其说是动态规划、状态转移,我更偏向认为它是贪心算法,是因为这类型的题十分人性化。推理过程也很贪心,局部最优形成总的最优。
-
今天把股票题全部手写了一遍,手推了一遍,花了5个小时,量变产生质变。309题从推理状态转移方程到完成代码只花费了10分钟。详情见如309.最佳买卖股票时机含冷冻期。
-
今晚我会发出打家劫舍、等差数列划分、背包的四种情况。
121. 买卖股票的最佳时机
做法一:状态解法、动态规划(4ms)每次的当前每个状态都是最优
// 第一版
public static int maxProfit(int[] prices) {
int[][] states = new int[prices.length][2];//0:买,1:卖
/*
* 状态变化解释:
* 卖:states[i][1] = Math.max(states[i-1][0]+prices[0] , states[i-1][1]);
* 当前如果要卖,则卖后最优解为:Math.max(当前的价格 + 之前购买后的剩余金额(负数) , 之前卖后的最优价格)
* 比如:当前价格为8,之前购买后手头金额最多为 -1,之前卖后的最优剩余为10,
* 此时 Math.max( 8+(-1) , 10);//这个时候不适合卖,还是之前的10更多,所以不修改最优解。
*
* 买:由于只能买一次,则买后最优解为:Math.max(前面的买后手头金额,-当前的价格);
* states[i][0] = Math.max(states[i - 1][0], - prices[i]);
*/
states[0][0] = -prices[0];
for (int i = 1; i < prices.length; i++) {
states[i][1] = Math.max(states[i-1][1],states[i - 1][0] + prices[i]);
//由于只能买一次,所以每次的买后的值,都要直接改变,而不是由之前的卖后值—当前的价格(二次购买)。
states[i][0] = Math.max(states[i - 1][0], - prices[i]);
}
return states[prices.length-1][1];
}
//优化空间,写完后会发现,我们只用了2个最优解的值。
public static int maxProfit(int[] prices) {
int[] states = new int[2];//0:买,1:卖
states[0] = -prices[0];
for (int i = 1; i < prices.length; i++) {
states[1] = Math.max(states[1],states[0] + prices[i]);
//由于只能买一次,所以每次的买后的值,都要直接改变,而不是由之前的卖后值—当前的价格(二次购买)。
states[0] = Math.max(states[0], - prices[i]);
}
return states[1];
}
做法二:暴力解法、贪心算法,每次得到最优解(1ms)
public int maxProfit(int[] prices) {
if(prices.length<2) return 0;
int min = prices[0];
int res = 0;
for(int price:prices){
if(min>=price){
min = price;
continue;
}
//每次直接得到最优解
res = Math.max(price-min,res);
}
return res;
}
122. 买卖股票的最佳时机 II
做法一:状态解法、动态规划(2ms)
public int maxProfit(int[] prices) {
int[] states = new int[2];
states[0] = -prices[0];
for (int i = 1; i < prices.length; i++) {
int pre1 = states[1];
states[1] = Math.max(states[1], states[0] + prices[i]);
//只有这里有改动,即每次买后的价格,为上一次最优的买后价格-当前价格:states[1]-prices[i];即允许多次购买,基于以前的最优卖后结果进行购买
states[0] = Math.max(states[0], pre1-prices[i]);
}
return states[1];
}
做法二:暴力解法、贪心算法,局部最优,得到整体最优。
public int maxProfit(int[] prices) {
int res = 0;
int n = prices.length;
for(int i=0;i<n-1;i++){
//1234,的局部最优是2-1=1,3-2=1,4-3=1,整体最优为 4-1 = 1+1+1 = 3
if(prices[i]<prices[i+1]){//遇到升值,直接进行单次买卖:12341215:在2、3、4、2、5卖出,局部最优
res = res + prices[i+1] - prices[i];
}
}
return res;
}
123. 买卖股票的最佳时机 III
public int maxProfit(int[] prices) {
int n = prices.length;
int[] states = new int[]{-prices[0], 0, -prices[0], 0};
for (int i = 1; i < n; i++) {
//保存上轮第一次买的最优价格,上轮第一次买最优价格+当前价格,Math.max()后可能为第一次卖的最优价格
int pre0 = states[0];
//保存上轮第二次买的最优价格,上轮第二次买最优价格+当前价格,Math.max()后可能为第二次卖的最优价格
int pre2 = states[2];
states[0] = Math.max(-prices[i], states[0]);//第一次买最优
states[2] = Math.max(states[2], states[1] - prices[i]);//第二次买最优
states[1] = Math.max(prices[i] + pre0, states[1]);//第一次卖最优
states[3] = Math.max(prices[i] + pre2, states[3]);//第二次卖最优
}
return states[3];
}
//解法二:最优解:不需要保存上一轮买后的最优解。
//如果这一轮的买后余额被替换,说明,这一轮买入是最便宜的,则这一轮的卖后余额不变,因为遇到最低价了!
//代码逻辑使得:它无法在最低价(亏本)的时候卖出!如下
// sell1 = Math.max(sell1, buy1 + prices[i]);
// sell2 = Math.max(sell2, buy2 + prices[i]);
public int maxProfit2(int[] prices) {
int n = prices.length;
int buy1 = -prices[0], sell1 = 0;
int buy2 = -prices[0], sell2 = 0;
for (int i = 1; i < n; ++i) {
buy1 = Math.max(buy1, -prices[i]);
sell1 = Math.max(sell1, buy1 + prices[i]);
buy2 = Math.max(buy2, sell1 - prices[i]);
sell2 = Math.max(sell2, buy2 + prices[i]);
}
return sell2;
}
188. 买卖股票的最佳时机 IV
方法一:直接求解
public int maxProfit(int k, int[] prices) {
if(k==0) return 0;
if(prices.length<2) return 0;
int[][] states = new int[k][2];//0:买入,1:卖出
// 不设置初始值,Math.max(0,-当前价格) 的返回值永远为0.
for (int i = 0; i < k; i++) {
states[i][0] = -prices[0];
}
for (int price : prices) {
for (int i = 0; i < k; i++) {
//只有当i==0时,需要特殊处理。
states[i][0] = Math.max(states[i][0], (i>0?states[i - 1][1]:0) - price);
states[i][1] = Math.max(states[i][1], (i>0?states[i][0]:states[0][0]) + price);
}
}
return states[k-1][1];
}
方法二:使用了上面两个卖股票的算法进行求解
(比方法一多判断了一种情况,并将这种情况进行特殊处理maxProfitNormal(prices)
,减少了分支)
public int maxProfit(int k, int[] prices) {
if(k==0) return 0;
if (k > prices.length / 2) {
return maxProfitNormal(prices);
}
int[][] states = new int[k][2];//0:买入,1:卖出
// [123. 买卖股票的最佳时机 III]的通用解
for (int i = 0; i < k; i++) {
states[i][0] = -prices[0];
}
for (int price : prices) {
for (int i = 0; i < k; i++) {
states[i][0] = Math.max(states[i][0], (i>0?states[i - 1][1]:0) - price);
states[i][1] = Math.max(states[i][1], (i>0?states[i][0]:states[0][0]) + price);
}
}
return states[k-1][1];
}
//[122. 买卖股票的最佳时机 II]的贪心求解
private int maxProfitNormal(int[] prices) {
int res = 0;
int n = prices.length;
for (int i = 0; i < n - 1; i++) {
//1234,的局部最优是2-1=1,3-2=1,4-3=1,整体最优为 4-1 = 1+1+1 = 3
if (prices[i] < prices[i + 1]) {//遇到升值,直接进行单次买卖:12341215:在2、3、4、2、5卖出,局部最优
res = res + prices[i + 1] - prices[i];
}
}
return res;
}
309. 最佳买卖股票时机含冷冻期
// 开始发生质变,推理到完成,仅用时10分钟
public static int maxProfit(int[] prices) {
int[] states = new int[3];//0买入,1卖出,2冷冻
/**
* states[0] = 当前买入最优解 = Math.max(当前买入最优解,当前冷冻最优解-当前价格)
* states[1] = 当前卖出最优解 = Math.max(当前卖出最优解,当前买入最优解+当前价格)
* states[2] = 当前冷冻最优解 = Math.max(当前冷冻最优解,上次卖出价格)
*/
states[0] = -prices[0];
for (int price:prices){
int pre1 = states[1];
states[0] = Math.max(states[0],states[2]-price);
states[1] = Math.max(states[1],states[0]+price);
states[2] = Math.max(states[2],pre1);
}
return states[1];
}
714. 买卖股票的最佳时机含手续费
public static int maxProfit(int[] prices, int fee) {
int buy = prices[0] + fee;//代价
int res = 0;
for (int price : prices) {
//代价更小,则
if (price + fee < buy) {
buy = price + fee;
} else if (price > buy) {
res += price - buy;
buy = price;//下次购买代价的底线,就是今天卖出的价格
}
}
return res;
}
剑指 Offer 63. 股票的最大利润
和第一道题一模一样
public int maxProfit(int[] prices) {
if(prices.length<2) return 0;
int min = prices[0];
int res = 0;
for(int price:prices){
if(min>=price){
min = price;
continue;
}
//每次直接得到最优解二
res = Math.max(price-min,res);
}
return res;
}