题目与题解
123.买卖股票的最佳时机III
题目链接:123.买卖股票的最佳时机III
代码随想录题解:123.买卖股票的最佳时机III
视频讲解:动态规划,股票至多买卖两次,怎么求? | LeetCode:123.买卖股票最佳时机III_哔哩哔哩_bilibili
解题思路:
其实就是沿用买卖股票II的思路,不同点在于有两次买卖,所以dp数组由n*2变为n*4。
但是一开始没想通怎么写,直接看答案。
看完代码随想录之后的想法
关键在于明确dp的含义,和递推公式的概念。
一天一共就有五个状态,
- 没有操作 (其实我们也可以不设置这个状态)
- 第一次持有股票
- 第一次不持有股票
- 第二次持有股票
- 第二次不持有股票
dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。
达到dp[i][1]状态,有两个具体操作:
- 操作一:第i天买入股票了,那么dp[i][1] = dp[i-1][0] - prices[i]
- 操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]
同理dp[i][2]也有两个操作:
- 操作一:第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
- 操作二:第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]
同理可推出剩下状态部分:
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
其实跟昨天的题是一样的,写的时候可以不用第0个状态,直接分4个状态。
class Solution {public int maxProfit(int[] prices) {if (prices.length <= 1) return 0;int[][] dp = new int[prices.length][4];dp[0][0] = -prices[0];dp[0][2] = -prices[0];for (int i = 1; i < prices.length; i++) {dp[i][0] = Math.max(dp[i-1][0], -prices[i]);dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);dp[i][2] = Math.max(dp[i-1][2], dp[i-1][1] - prices[i]);dp[i][3] = Math.max(dp[i-1][3], dp[i-1][2] + prices[i]);}return dp[dp.length - 1][3];}
}
遇到的困难
感觉dp的定义有点绕,但又很套路,复习的时候再仔细想想吧。
188.买卖股票的最佳时机IV
题目链接:188.买卖股票的最佳时机IV
代码随想录题解:188.买卖股票的最佳时机IV
视频讲解:
动态规划来决定最佳时机,至多可以买卖K次!| LeetCode:188.买卖股票最佳时机4_哔哩哔哩_bilibili
解题思路:
有前一题打底,其实很容易看出规律来,设置dp为n* 2*k的数组,2*k表示k次买入买出的状态,则递推公式中,dp[i][j]的j如果是偶数,表示持有股票状态,其值为
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1] - prices[i]);
dp[i][j]的j如果是奇数,表示持有股票状态,其值为
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1] + prices[i]);
初始化时,将j为奇数的dp[0][j]初始化为-prices[i]即可。
class Solution {public int maxProfit(int k, int[] prices) {if (prices.length <= 1) return 0;int[][] dp = new int[prices.length][k * 2];for (int i = 0; i < 2 * k; i = i+2) {dp[0][i] = -prices[0];}for (int i = 1; i < prices.length; i++) {dp[i][0] = Math.max(dp[i-1][0], -prices[i]);for (int j = 1; j < 2 * k; j += 2) {dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1] + prices[i]);}for (int j = 2; j < 2 * k; j += 2) {dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1] - prices[i]);}}return dp[dp.length - 1][2*k-1];}
}
看完代码随想录之后的想法
这个思路没啥好说的,写法可以优化一点,为了方便计算,可以设置一个第0天,初始化为0,就不用对第一天的数据做特殊处理了。另外,可以用一个循环同时更新奇偶数下标。
class Solution {public int maxProfit(int k, int[] prices) {if (prices.length == 0) return 0;// [天数][股票状态]// 股票状态: 奇数表示第 k 次交易持有/买入, 偶数表示第 k 次交易不持有/卖出, 0 表示没有操作int len = prices.length;int[][] dp = new int[len][k*2 + 1];// dp数组的初始化, 与版本一同理for (int i = 1; i < k*2; i += 2) {dp[0][i] = -prices[0];}for (int i = 1; i < len; i++) {for (int j = 0; j < k*2 - 1; j += 2) {dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);dp[i][j + 2] = Math.max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);}}return dp[len - 1][k*2];}
}
还有一种一维的写法,不太看得懂,先码吧。
//版本三:一维 dp数组 (下面有和卡哥邏輯一致的一維數組JAVA解法)
class Solution {public int maxProfit(int k, int[] prices) {if(prices.length == 0){return 0;}if(k == 0){return 0;}// 其实就是123题的扩展,123题只用记录2次交易的状态// 这里记录k次交易的状态就行了// 每次交易都有买入,卖出两个状态,所以要乘 2int[] dp = new int[2 * k];// 按123题解题格式那样,做一个初始化for(int i = 0; i < dp.length / 2; i++){dp[i * 2] = -prices[0];}for(int i = 1; i <= prices.length; i++){dp[0] = Math.max(dp[0], -prices[i - 1]);dp[1] = Math.max(dp[1], dp[0] + prices[i - 1]);// 还是与123题一样,与123题对照来看// 就很容易啦for(int j = 2; j < dp.length; j += 2){dp[j] = Math.max(dp[j], dp[j - 1] - prices[i-1]);dp[j + 1] = Math.max(dp[j + 1], dp[j] + prices[i - 1]);}}// 返回最后一次交易卖出状态的结果就行了return dp[dp.length - 1];}
}
遇到的困难
无
今日收获
加强了一下二维dp的做法,记住套路吧。