买卖股票问题是经典的动态规划问题(有时候贪心也能解)。
121. 买卖股票的最佳时机
- dp[0]表示第i天不持有股票的最佳收益,dp[1]表示第i天持有股票的最佳收益
- dp[0] = max(dp[0], dp[1] + prices[i]),dp[0]可能由前一天就不持有股票或者前一天持有股票又卖掉了得出
dp[1] = max(dp[1], -prices[i]),dp[1]可能由前一天持有股票或者今天才买入股票得出
- 初始化dp[0] = 0, dp[1] = -prices[0]。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<int> dp(2, 0);
dp[1] = -prices[0];
for (int i = 1; i < n; i++) {
int temp = dp[0];
dp[0] = max(dp[0], dp[1] + prices[i]);
dp[1] = max(dp[1], -prices[i]);
}
return dp[0] > dp[1] ? dp[0] : dp[1];
}
};
122. 买卖股票的最佳时机 II
这题和上面那道题差不多,只是现在你可以多次买入多次卖出了,dp数组定义不变,dp[1]的递推方程要变一点,之前只能买入一次,所以在买入前收益肯定是0,所以dp[1] = max(dp[1], -prices[i]),现在买入前收益不一定是0了,可能是有收益的,所以 dp[1] = max(dp[1], dp[0] - prices[i])。代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<int> dp(2, 0);
int n = prices.size();
dp[1] = -prices[0];
for (int i = 1; i < n; i++) {
dp[0] = max(dp[0], dp[1] + prices[i]);
dp[1] = max(dp[1], dp[0] - prices[i]);
}
return dp[0] > dp[1] ? dp[0] : dp[1];
}
};
123. 买卖股票的最佳时机 III
这道题属实有点难度,但是如果明白了前面两道题的本质还是可以尝试解决的。
这三道题的动态规划的本质是什么,用dp表示当前所处的状态的,例如第一题中dp[1]表示第i天持有股票,持有股票不代表第i天就买了股票,也可能是第i-1天处于持有的状态,在这道题中,共有四种状态
- 第一次买入
- 第一次卖出
- 第二次买入
- 第二次卖出
例如dp[2]不代表第i天干了什么事,可能啥也没干,只是前面一天也处于dp[2]的状态。明白了这一点,转移方程就可以像前面两道题那样自然而然地写出来了。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<int> dp(4, 0);
dp[0] = -prices[0];
dp[2] = -prices[0];
for (int i = 1; i < n; i++) {
dp[0] = max(dp[0], -prices[i]);
dp[1] = max(dp[1], dp[0] + prices[i]);
dp[2] = max(dp[2], dp[1] - prices[i]);
dp[3] = max(dp[3], dp[2] + prices[i]);
}
return dp[1] > dp[3] ? dp[1] : dp[3];
}
};
188. 买卖股票的最佳时机 IV
这题都没什么好说的,就是上面的题的翻版,只需要把状态都正确地表示出来即可,我这里奇数是买入,偶数是卖出。
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
if (prices.size() == 0)
return 0;
vector<int> dp(2 * k + 1, 0);
for (int i = 1; i < 2 * k + 1; i += 2) {
dp[i] = -prices[0];
}
int n = prices.size();
for (int i = 1; i < n; i++) {
for (int g = 1; g < 2 * k; g += 2) {
dp[g] = max(dp[g], dp[g - 1] - prices[i]);
dp[g + 1] = max(dp[g + 1], dp[g] + prices[i]);
}
}
int ans = 0;
for (int i = 2; i < 2 * k + 1; i += 2) {
ans = dp[i] > ans ? dp[i] : ans;
}
return ans;
}
};
309. 最佳买卖股票时机含冷冻期
同上面说的一样,买卖股票的问题主要是看每一天有几种状态,然后写出每种状态的转移方程。这里一共有三种状态
- 持有股票
- 不持有股票并且今天没卖出股票
- 不持有股票并且今天卖出了股票
对于第0种情况,可能是来着昨天的持有股票状态或昨天没卖出今天买入了。即dp[0] = max(dp[0], dp[1] - prices[i]);
对于第1种情况,今天没卖出又不持有股票,只能来自昨天的不持有了,即dp[1] = max(dp[1], dp[2]);
对于第2种情况,今天卖出了股票,所以是dp[2] = dp[0] + prices[i]。
所以代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<int> dp(3, 0);
dp[0] = -prices[0];
int n = prices.size();
for (int i = 1; i < n; i++) {
dp[0] = max(dp[0], dp[1] - prices[i]);
dp[1] = max(dp[1], dp[2]);
dp[2] = dp[0] + prices[i];
}
return dp[1] > dp[2] ? dp[1] : dp[2];
}
};
这里说一下三个递推方程写的顺序为什么是这样,其实写的顺序是看递推方程的意义,dp[0]要用到昨天的dp[1],所以在dp[0]前半年修改dp[1], dp[1]要用到昨天的dp[1]和dp[2],所以在dp[1]前不能先修改dp[2]。
714. 买卖股票的最佳时机含手续费
做到这里这题就很简单了,动规的时候加上手续费即可。
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
vector<int> dp(2, 0);
dp[0] = -prices[0];
int n = prices.size();
for (int i = 1; i < n; i++) {
dp[0] = max(dp[0], dp[1] - prices[i]);
dp[1] = max(dp[1], dp[0] + prices[i] - fee);
}
return dp[1];
}
};
其实这题也可以用贪心来做,我觉得贪心的做法比动规要难想多了,可以看看LeetCode的官方题解。