关注公众号“算法岗从零到无穷”,并在微信后台回复“动态规划”,动态规划做法秘籍送给你。请帮忙分享给你的朋友喔!
今天我们继续股票问题,新来的朋友可以阅读我们上一则推送。我将告诉大家一个通用的方法,一个方法,解决6道股票问题。同时也要感谢leetcode用户fun4LeetCode,本文参考:https://urlify.cn/zYbu2a
我们的动态规划基本框架如下:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
解释:第i天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择“闲着”,
所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我“卖了”,
所以我今天没有持有股票了。
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
解释:第i天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择“闲着”,
所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择“买入”,
所以今天我就持有股票了。
123. 买卖股票的最佳时机 III
这道题中k = 2,和上期题目的情况稍微不同,因为上期题目最后都可以化简状态转移方程,与k无关。这道题无法化简,那么我们就把k的情况穷举出来,将状态转移方程直接翻译。
public int maxProfit(int[] prices) {
int max_k = 2;
int[][][] dp = newint[prices.length+1][max_k+1][2];
for (int k=0;k<=max_k;k++) {
dp[0][k][0]=0;
dp[0][k][1]=Integer.MIN_VALUE;
}
for (int i = 1; i <= prices.length; i++) {
for (int k = max_k; k >= 1; k--) {
dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i-1]);
dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i-1]);
}
}
return dp[prices.length][max_k][0];
}
其实啊,这里 k 取值范围比较小,所以可以不用 for 循环,直接把 k = 1 和 2 的情况手动列举出来:
public int maxProfit(int[] prices) {
int buyOne = Integer.MAX_VALUE;
int SellOne = 0;
int buyTwo = Integer.MAX_VALUE;
int SellTwo = 0;
for(int p : prices) {
buyOne = Math.min(buyOne, p);
SellOne = Math.max(SellOne, p - buyOne);
buyTwo = Math.min(buyTwo, p - SellOne);
SellTwo = Math.max(SellTwo, p - buyTwo);
}
return SellTwo;
}
上面这段代码大家都看过吧,是公认的正解。大家之前在阅读的时候,是不是很费解,这。。。这怎么想得到!那么现在用我们的框架一套,就很好理解了!
值得注意的是,这可是一道hard题,你看,跟着我们做算法,一不小心hard题也做出来了!
188. 买卖股票的最佳时机 IV
这道题是上面一道题的一般情况,也是一道hard题。很简单,我们把上面那道题的参数int maxK=2,改成int maxK=k就行了。但是程序过不了,内存会溢出。
我们简单分析一下,一次交易由买入和卖出构成,至少需要两天。所以说有效的限制 k 应该不超过 n/2,如果超过,就没有约束作用了,这种情况是之前解决过的,参见上一期推送的第122题。所以我们就单独那这个特例出来,按照上次的方式处理就ok了。代码如下:
int quickSolve(int[] prices) {
int len = prices.length, profit = 0;
for (int i = 1; i < len; i++)
if (prices[i] > prices[i - 1])
profit += prices[i] - prices[i - 1];
return profit;
}
int maxProfit(int k, int[] prices) {
int n = prices.length;
if (k > n / 2)
return quickSolve(prices);
int[][][] dp = newint[n+1][k+1][2];
for (int kk=0;kk<=k;kk++) {
dp[0][kk][0]=0;
dp[0][kk][1]=Integer.MIN_VALUE;
}
for (int i = 1; i <= n; i++) {
for (int kk = k; kk >= 1; kk--) {
dp[i][kk][0] = Math.max(dp[i - 1][kk][0], dp[i - 1][kk][1] + prices[i-1]);
dp[i][kk][1] = Math.max(dp[i - 1][kk][1], dp[i - 1][kk - 1][0] - prices[i-1]);
}
}
return dp[n][k][0];
}
值得注意的是,我们的代码没有经过优化,所以提交之后我们会发现效率并没有处在很前列,我们用这一个“股票系列”目的是想告诉大家一个通解的方法,优化动态规划的板块我们会和后面的章节一起讲,有兴趣的读者可以自己先尝试一下,找找规律。
在动态规划中,为了处理为0的边界条件,我们建议dp数组大小要比给出的大1,简单来说就是int[][][] dp = new int[n+1][k+1][2];这个代码的逻辑。
好了,这是我们动态规划的一大系列,动态规划还有一大系列是“背包问题”,不要着急,我们也会讲解。
关注公众号“算法岗从零到无穷”,并在微信后台回复“动态规划”,动态规划做法秘籍送给你。请帮忙分享给你的朋友喔!