前言
《买卖股票的最佳时机》问题合集
问题描述
代码
方案1
本题目限制只能进行一次交易,因此收益最优的策略一定是:在价格最低的时刻 t0 买入,价格最高的时刻 t1 卖出,注意 t0<t1 即可。
1 2 3 4 5 6 7 8 9 10 11 12
| class Solution { static final int INF = 0x3f3f3f3f; public int maxProfit(int[] prices) { int minVal = INF; int maxP = -INF; for (int i = 0; i < prices.length; ++i) { minVal = Math.min(prices[i], minVal); maxP = Math.max(maxP, prices[i] - minVal); } return maxP; } }
|
通用思路
dp 状态机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { static final int INF = 0x3f3f3f3f; public int maxProfit(int[] prices) { int n = prices.length; int[][] f = new int[n + 1][3]; for (int i = 0; i <= n; ++i) { f[i][0] = -INF; f[i][1] = -INF; f[i][2] = -INF; } f[0][0] = 0; for (int i = 1; i <= n; ++i) { int x = prices[i - 1]; f[i][0] = f[i - 1][0]; f[i][1] = Math.max(f[i - 1][1], f[i - 1][0] - x); f[i][2] = Math.max(f[i - 1][2], f[i - 1][1] + x); } return Math.max(f[n][2],f[n][0]); } }
|
题目描述
代码
方案1
由于可以多次买入卖出,因此找出所有的价格上升区段,将所获的收益加起来即为最大利润
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public int maxProfit(int[] arr) { if (arr.length == 1) return 0;
int ans = 0; for (int i = 1; i < arr.length; i++) { if (arr[i] > arr[i-1]) { ans += (arr[i] - arr[i-1]); } }
return ans; } }
|
通用思路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { static final int INF = 0x3f3f3f3f; public int maxProfit(int[] prices) { int n = prices.length; int[][] f = new int[n + 1][2]; for (int i = 0; i <= n; ++i) { f[i][0] = -INF; f[i][1] = -INF; } f[0][1] = 0; int x; for (int i = 1; i <= n; ++i) { x = prices[i - 1]; f[i][0] = Math.max(f[i - 1][0], f[i - 1][1] - x); f[i][1] = Math.max(f[i - 1][1], f[i - 1][0] + x); } return f[n][1]; } }
|
注意初始化,如果股票价格不为负的话,将每一天的卖出股票的最大收益初始值设置为0即可,就不用特地为第0天初始化了。
- 再进一步,为什么要将
f[i][0]=-INF
设置为负最大值? - 实际上只需将第0天的初始值
f[0][0]
买入股票的最大收益设置为-INF
即可
第0天没有股票可购买,买入股票的最大收益要设置为一个无意义并且无影响的值。
不影响判断,是指
f[1][0]=max{f[0][0],f[0][1]−x}
而实际上 f[1][0] 只能等于 −x
1 2 3 4 5 6 7 8
| int[][] f = new int[n + 1][2]; f[0][0] = -INF; int x; for (int i = 1; i <= n; ++i) {
|
关于初始值的设定,在问题五中继续拓展思考。
如果对交易次数进行限制呢?
问题描述
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Solution { static final int INF = 0x3f3f3f3f; public int maxProfit(int[] prices) { int n = prices.length; int[][] f = new int[n + 1][5]; f[0][1] = -INF; f[0][3] = -INF; for (int i = 1; i <= n; ++i) { int x = prices[i - 1]; f[i][0] = f[i - 1][0]; f[i][1] = Math.max(f[i - 1][1], f[i - 1][0] - x); f[i][2] = Math.max(f[i - 1][2], f[i - 1][1] + x); f[i][3] = Math.max(f[i - 1][3], f[i - 1][2] - x); f[i][4] = Math.max(f[i - 1][4], f[i - 1][3] + x); } return Math.max(f[n][0], Math.max(f[n][2], f[n][4])); } }
|
限定交易次数为 K
问题描述
代码
dp 状态机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Solution { static final int INF = 0x3f3f3f3f; public int maxProfit(int k, int[] prices) { int n = prices.length; int[][][] f = new int[n + 1][k + 1][2];
for (int j = 0; j <= k; ++j) { f[0][j][0] = -INF; } for (int i = 1; i <= n; ++i) { int x = prices[i - 1]; for (int j = 1; j <= k; ++j) { f[i][j][0] = Math.max(f[i - 1][j - 1][1] - x, f[i - 1][j][0]); f[i][j][1] = Math.max(f[i - 1][j][0] + x, f[i - 1][j][1]); } } int maxP = 0; for (int j = 0; j <= k; ++j) { maxP = Math.max(f[n][j][1], maxP); } return maxP; } }
|
关于初始化,参看以上代码。
从卖出股票合法性方面考虑,进行初始化。
1 2 3 4 5 6 7 8 9
| for (int i = 0; i <= n; ++i) { for (int j = 0; j <= k; ++j) { f[i][j][0] = -INF; f[i][j][1] = -INF; } }
for (int i = 0; i < n; ++i) f[i][0][1] = 0;
|
推荐采用买入合法性的思路进行初始化,
因此初始化时只需考虑,第0天买入股票的合法性
实际上,第0天无法买入任何股票。因此第0天的买入收益设置一个无意义且无影响的 −INF 即可。
以这种思路回来看第一题,初始条件如何设置呢?
答:f[0][1] = -INF
ps:在第一题中,状态码1代表买入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { static final int INF = 0x3f3f3f3f; public int maxProfit(int[] prices) { int n = prices.length; int[][] f = new int[n + 1][3]; f[0][1] = -INF; for (int i = 1; i <= n; ++i) { int x = prices[i - 1]; f[i][0] = f[i - 1][0]; f[i][1] = Math.max(f[i - 1][1], f[i - 1][0] - x); f[i][2] = Math.max(f[i - 1][2], f[i - 1][1] + x); } return Math.max(f[n][2],f[n][0]); } }
|
后话
其实仔细研究发现,这几题的状态转移方程,都只与第 i 天、第 i−1 天的状态有关,因此可以使用滚动数组进行空间占用上的优化。
以股票买卖的最佳时机IV为例:
唯一需要注意的是,对交易轮次循环时,要从后往前。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Solution { static final int INF = 0x3f3f3f3f; public int maxProfit(int k, int[] prices) { int n = prices.length; int[][] f = new int[k+1][2]; for (int j = 0; j <= k; ++j) { f[j][0] = -INF; } for (int i = 1; i <= n; ++i) { int x = parices[i - 1]; for (int j = k; j >= 1; --j) { f[j][0] = Math.max(f[j - 1][1] - x, f[j][0]); f[j][1] = Math.max(f[j][0] + x, f[j][1]); } } int maxP = -INF; for (int j = 0; j <= k; ++j) { maxP = Math.max(f[j][1], maxP); } return maxP; } }
|
最终结果
收工。