买卖股票的最佳时机(I - III) 动态规划入门
股票问题(动态规划入门)
1. 买卖股票的最佳时机I
题意:给定股票每一天的价格
prices
,只允许买入一次卖出一次,求能获得的最大收益。
思路
状态设计:
f[i][0] 表示第 i 天持有股票的最大收益
f[i][1] 表示第 i 天不持有股票的最大收益
(注意第 i 天的股票价格为 prices[i-1])
状态转移:
f[i][0]
可以由f[i-1][0]
或者-prices[i-1]
转移而来(第 i 天持有股票,要么前一天就持有股票,即f[i-1][0]
,要么在这一天之前,什么也没有操作,即收益为 0,那么第 i 天购入股票,花费prices[i-1]
,总收益为-prices[i-1]
)。f[i][1]
可以由f[i-1][1]
或者f[i-1][0] + prices[i-1]
转移而来(第 i 天不持有股票,要么前一天就不持有股票,即f[i-1][1]
,要么在这一天之前持有,第 i 天卖出股票,收益prices[i-1]
,总收益为f[i-1][0] + prices[i-1]
)。
即有转移方程(最大收益,从两个转移中取 max):
f[i][0] = max(f[i-1][0], -prices[i-1]);
f[i][1] = max(f[i-1][1], f[i-1][0] + prices[i-1]);
初始值设定:
f[0][0]
:表示“还没开始操作时,手里有股票”的最大收益。实际上这种情况在现实中并不存在,但为了让后续转移方程统一,我们假设如果在第1天(下标0)买入股票,收益就是-prices[0]
。这样做的本质是让f[0][0]
对应于prices[-1]
,即还未开始操作的状态。f[0][1]
:表示“还没开始操作时,手里没有股票”的最大收益,这种情况下收益自然是0。
这样设置的好处是,后续每一天的状态转移都可以直接用统一的公式,不需要对第一天做特殊处理。
代码实现:
int f[100010][2];
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
// 还可用滚动数组优化
f[0][0] = -prices[0];
for (int i = 1; i <= n; i++) {
f[i][0] = max(f[i-1][0], -prices[i-1]);
f[i][1] = max(f[i-1][1], f[i-1][0] + prices[i-1]);
}
return f[n][1];
}
};
题外话:
这题当然还有更优解,维护当日之前的最小值,用当日的值减去该最小值,以此更新答案即可。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int mn = prices[0], res = 0;
for (int i = 0; i < n; i++) {
mn = min(mn, prices[i]);
res = max(res, prices[i] - mn);
}
return res;
}
};
2. 买卖股票的最佳时机II
题意:给定股票每一天的价格
prices
,允许买入卖出多次,但是任意时刻最多只能持有一支股票,即买入一支股票后,卖出其才能再次购买股票,求能获得的最大收益。
思路
这题与买卖股票的最佳时机 I 的最大区别就是这里可以进行多次买入卖出操作。在状态设计上不变,状态转移上有所变化。
状态设计(与 I 相同):
f[i][0] 表示第 i 天持有股票的最大收益
f[i][1] 表示第 i 天不持有股票的最大收益
(注意第 i 天的股票价格为 prices[i-1])
状态转移:
f[i][0]
可以由f[i-1][0]
或者f[i-1][1] - prices[i-1]
转移而来(第 i 天持有股票,要么前一天就持有股票,即f[i-1][0]
,要么在这一天之前不持有股票,即f[i-1][1]
,那么第 i 天购入股票,花费prices[i-1]
,总收益为f[i-1][1] - prices[i-1]
)。f[i][1]
可以由f[i-1][1]
或者f[i-1][0] + prices[i-1]
转移而来(第 i 天不持有股票,要么前一天就不持有股票,即f[i-1][1]
,要么在这一天之前持有,第 i 天卖出股票,收益prices[i-1]
,总收益为f[i-1][0] + prices[i-1]
)。
即有转移方程(最大收益,从两个转移中取 max):
f[i][0] = max(f[i-1][0], f[i-1][1] - prices[i-1]);
f[i][1] = max(f[i-1][1], f[i-1][0] + prices[i-1]);
初始值设定与 I 相同,不再赘述
代码实现:
int f[100010][2];
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
f[0][0] = -prices[0];
f[0][1] = 0;
for (int i = 1; i < n; i++) {
f[i][0] = max(f[i-1][0], f[i-1][1] - prices[i]);
f[i][1] = max(f[i-1][1], f[i-1][0] + prices[i]);
}
return f[n-1][1];
}
};
题外话:
本题也可以用贪心法直接求解:只要今天的价格比昨天高,就把差价加到收益里。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0;
for (int i = 1; i < prices.size(); i++) {
if (prices[i] > prices[i-1]) res += prices[i] - prices[i-1];
}
return res;
}
};
3. 买卖股票的最佳时机III
题意:给定股票每一天的价格
prices
,允许买入卖出最多两次,但是任意时刻最多只能持有一支股票,即买入一支股票后,卖出其才能再次购买股票,求能获得的最大收益。
思路
状态设计:
f[i][0] 表示第 i 天第一次买入股票后持有的最大收益
f[i][1] 表示第 i 天第一次卖出股票后不持有的最大收益
f[i][2] 表示第 i 天第二次买入股票后持有的最大收益
f[i][3] 表示第 i 天第二次卖出股票后不持有的最大收益
(注意第 i 天的股票价格为 prices[i-1])
状态转移:
f[i][0]
可以由f[i-1][0]
或-prices[i-1]
转移而来(第一次买入,或者之前就持有)。f[i][1]
可以由f[i-1][1]
或f[i-1][0] + prices[i-1]
转移而来(第一次卖出,或者之前就已卖出)。f[i][2]
可以由f[i-1][2]
或f[i-1][1] - prices[i-1]
转移而来(第二次买入,或者之前就持有)。f[i][3]
可以由f[i-1][3]
或f[i-1][2] + prices[i-1]
转移而来(第二次卖出,或者之前就已卖出)。
即有转移方程:
f[i][0] = max(f[i-1][0], -prices[i-1]);
f[i][1] = max(f[i-1][1], f[i-1][0] + prices[i-1]);
f[i][2] = max(f[i-1][2], f[i-1][1] - prices[i-1]);
f[i][3] = max(f[i-1][3], f[i-1][2] + prices[i-1]);
初始值设定:
f[0][0] = -prices[0]
,表示第1天买入第一次股票后的收益。f[0][1] = 0
,表示第1天卖出第一次股票后的收益。f[0][2] = -prices[0]
,表示第1天买入第二次股票后的收益(只能在第一次卖出后买入,但初始化时可设为同上,后续转移会自动修正)。f[0][3] = 0
,表示第1天卖出第二次股票后的收益。
代码实现:
int f[100010][4];
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
f[0][0] = f[0][2] = -prices[0];
f[0][1] = f[0][3] = 0;
for (int i = 1; i < n; i++) {
f[i][0] = max(f[i-1][0], -prices[i]);
f[i][1] = max(f[i-1][1], f[i-1][0] + prices[i]);
f[i][2] = max(f[i-1][2], f[i-1][1] - prices[i]);
f[i][3] = max(f[i-1][3], f[i-1][2] + prices[i]);
}
return f[n-1][3];
}
};