算法训练营day42 | {121.买卖股票的最佳时机, 122.买卖股票的最佳时机II, 123.买卖股票的最佳时机III}

第九章动态规划part08,股票问题是一个动态规划的系列问题。

121. 买卖股票的最佳时机

1.1 解题分析

定义一个dp数组,dp[j]表示在第j天卖出时,能得到的最大利润。依靠前面几天买入的最低价格做减法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int maxProfit(int[] prices) {
int[] dp = new int[prices.length];
int minIn = prices[0];
for (int i = 1; i < prices.length; i++) {
minIn = Math.min(minIn, prices[i]);
dp[i] = prices[i] > minIn ? prices[i] - minIn : 0;
}
int profit = 0;
for (int p : dp) {
profit = Math.max(p, profit);
}
return profit;
}
}

我的解法,dp[prices.size() - 1]不是题目要求的解,还得再遍历一次dp数组,其实是贪心解法,不是dp。

1.2 贪心解法

因为股票就买卖一次,那么贪心怎么贪?取最左最小值,取最右最大值,得到的差值就是最大利润。其实就是我上面的代码,精简一下,不需要记录dp数组,只需要维护一个当前的最大利润值:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int maxProfit(int[] prices) {
int profit = 0;
int minIn = prices[0];
for (int i = 1; i < prices.length; i++) {
minIn = Math.min(minIn, prices[i]);
profit = Math.max(profit, prices[i] - minIn);
}
return profit;
}
}

1.3 动态规划解法

首先dp数组需要定义为一个二维的数组,因为每天所能获得的最大利润,均有持有和不持有两种状态,没法用一维数组来表示。其次,用“持有”来描述状态,比买入、卖出能包含更多情况。持有不代表就是当天买入,也有可能是昨天就买入了,今天保持持有的状态。

DP五部曲:

  1. dp数组定义:
    • dp[i][0] 表示第i天持有股票所得最多现金,一开始现金是0,持有股票之后可以是负数。
    • dp[i][1] 表示第i天不持有股票所得最多现金。
  2. 递推公式:
    • dp[i][0] 可以从下面两个状态转移过来。
      • 前一天已经持有 dp[i - 1][0]
      • 今天买入并持有 -prices[i]
    • dp[i][1] 可以从
      • 前一天已经不持有 dp[i - 1][1]
      • 今天卖出并不持有 dp[i - 1][0] + prices[1]
  3. dp数组初始化:
    • dp[0][0] = -prices[0]
    • dp[0][1] = 0
  4. 遍历顺序:从前往后
  5. 打印dp数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = - prices[0];
dp[0][1] = 0;

for (int i = 1; i < n; 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]);
}
return dp[n - 1][1];
}
}

122. 买卖股票的最佳时机II

2.1 解题分析

和上一题的唯一的区别:股票买卖可以发生多次。(限制:最多持有一支股票)

2.2 解题小结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = - prices[0];
dp[0][1] = 0;

for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
}
return dp[n - 1][1];
}
}

123. 买卖股票的最佳时机III

3.1 解题分析

一天一共有5个状态,拆解如下:

  • 0: 不操作
  • 1: 第一次持有
  • 2: 第一次不持有
  • 3: 第二次持有
  • 4: 第二次不持有

DP五部曲:

  1. dp数组定义:
    • dp[i][j] i表示第i天 j表示状态[0-4],dp[i][j]表示第i天状态j所剩最大现金
  2. 递推公式:
    • dp[i][0] = dp[i - 1][0] 不操作,延续前一天的状态
    • dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]) 其实第一个状态没有必要设置,第一次持有之前手里的现金就是0。
    • dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i])
    • 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])
  3. dp数组初始化:
    • dp[0][0] = 0
    • dp[0][1] = -prices[0]
    • dp[0][2] = 0
    • dp[0][3] = -prices[0]
    • dp[0][4] = 0
  4. 遍历顺序:从前往后
  5. 打印dp数组。

3.2 解题小结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][5];
dp[0][1] = - prices[0];
dp[0][3] = - prices[0];

for (int i = 1; i < n; i++) {
dp[i][1] = Math.max(dp[i - 1][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]);
dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
}

return dp[n - 1][4];
}
}

4. 今日收获

  • 补打卡命好苦,买卖股票问题,关键是理解“持有”,拆解清楚状态。第一次接触,动态规划的dp数组定义、以及递推公式都不怎么容易想到。
  • 学习时长:3.5小时

算法训练营day42 | {121.买卖股票的最佳时机, 122.买卖股票的最佳时机II, 123.买卖股票的最佳时机III}
http://paopaotangzu.xyz/cn/day42_leetcode/
作者
PROTON TANG
发布于
2026年1月31日
许可协议