本篇主要详细学习买卖股票的最佳时机系列的题目

买卖股票的最佳时机

题目:

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

思路

前面我们已经做了一道树形dp的题目, 所以我们的思路就可以往这方面靠一下。

回归到题目 本身, 他需要从这笔交易中获取的最大利润 ,而我们就需要再相对最小的价格时买入 ,在相对最大的价格时卖出。这样的到的利润才是最大 。为什么是用相对, 因为我们不能再买入前就卖出, 这不符合逻辑 也不符合题意。

直接用dp的思路来思考。

他要的是最大利润, 利润就是我们所获得的现金, 而我们所获得的现金就是买入后自己钱包所剩的现金 + 卖出后我们获得的现金。

用两个空间来记录我们买入股票时的现金数 ,和 卖出股票时的现金数

所以我们就可以用一个二维数组来记录我们收获的现金

1
2
dp[i][0] : 第i天时,持有股票时 ,我们所拥有的最大现金 为dp[i][0]
dp[i][1] : 第i天时,不持有股票时, 我们所拥有的最大现金为dp[i][1]

持有股票我们可以**分为两种情况: **

  • 再第i天之前,我们已经买入了股票, 今天(第i天)我们继续持有
  • 今天(第i天),买入股票

这两种状态, 我们都可以作为持有股票

同样的不持有股票, 我们也可以分为两种情况:

  • 再第i天之前 就不持有, 今天(第i天)也不持有
  • 今天(第i天) ,把股票买了

所以根据上述我们的思路,就可以得出dp式

  1. dp含义

正如前面的递推 ,dp含义就是

1
2
dp[i][0] : 第i天时,持有股票时 ,我们所拥有的最大现金 为dp[i][0]
dp[i][1] : 第i天时,不持有股票时, 我们所拥有的最大现金为dp[i][1]
  1. dp公式

因为dp的含义是得到最大现金 , 而我们只有每一步都是得到最大值 ,那么我们的结果才是最大的。

推导方程就是我们之前的持有股票 和 不持有股票的两种情况

1
2
dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
  1. dp初始化
1
2
3
dp[0][0] = -prices[0]; //第一天我们持有股票 那么一定是当天买入
dp[0][1] = 0; //第一天我们不持有股票 获得的最大现金就一定是 0 ,因为我们本来的现金默认就是0 ,没买自然没欠钱

  1. dp遍历顺序

从递推公式可以看出dp[i]都是由dp[i - 1]推导出来的,那么一定是从前向后遍历。

  1. 打印dp
天数 持有股票的最大现金 不持有股票的最大现金
0 -7 0
1 -1 0
2 -1 4
3 -1 4
4 -1 5
5 -1 5

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int length = prices.length;
int[][] dp = new int[length][2];
int result = 0;
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]);
}
return dp[length - 1][1];
}
}

买卖股票的最佳时机II

题目:

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 10 ^ 4
  • 0 <= prices[i] <= 10 ^ 4

思路

这道题相比于之前的题目最大的区别就是 你可以尽可能地完成更多的交易(多次买卖一支股票)。

但是题目的条件也很明确 : 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

也就是我们可以买卖完一支股票之后 ,还可以再进行买卖其他的股票。

思路和上道题 是一摸一样的 ,唯一有区别的就是我们持有股票时的最大的现金的两种情况需要发生变化

1
2
3
4
//持有股票有两种情况
//1. 之前就持有 ,那么现在继续持有
//2. 当前买入 ,但是必须将手头的股票卖掉
dp[i][0] = Math.max(dp[i-1][0] , dp[i-1][1] - prices[i]); //因为不能再同一天出售 ,所以我们就必须在前一天出售调股票,所以就是前一天不持有股票的最大现金 - 今天的股票价格

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution 
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][2]; // 创建二维数组存储状态
dp[0][0] = 0; // 初始状态
dp[0][1] = -prices[0];
for (int i = 1; i < n; ++i) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); // 第 i 天,没有股票
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); // 第 i 天,持有股票
}
return dp[n - 1][0]; // 卖出股票收益高于持有股票收益,因此取[0]
}
}

买卖股票的最佳时机III

题目:

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1: 输入:prices = [3,3,5,0,0,3,1,4] 输出:6 解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3。

示例 2: 输入:prices = [1,2,3,4,5] 输出:4 解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3: 输入:prices = [7,6,4,3,1] 输出:0 解释:在这个情况下, 没有交易完成, 所以最大利润为0。

示例 4: 输入:prices = [1] 输出:0

提示:

  • 1 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^5

思路

本题, 他要求最多可以完成两笔交易 。

那么相较于之前的题目 ,我们需要记录两次交易的状态。

所以dp数组的宽度就需要扩大

1
2
3
4
5
dp[0][0] 无操作 直接设置为 0 即可
dp[i][1] : 第i天时,第一次所持有股票时 ,我们所拥有的最大现金 为dp[i][1]
dp[i][2] : 第i天时,第一次不持有股票时, 我们所拥有的最大现金为dp[i][2]
dp[i][3] : 第i天时,第二次所持有股票时 ,我们所拥有的最大现金 为dp[i][3]
dp[i][4] : 第i天时,第二次不持有股票时, 我们所拥有的最大现金为dp[i][4]

那么得到的状态也都是相应的有两种状态

image-20230417193953398

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (prices.length == 0) return 0;

/*
* 定义 5 种状态:
* 0: 没有操作, 1: 第一次买入, 2: 第一次卖出, 3: 第二次买入, 4: 第二次卖出
*/
int[][] dp = new int[len][5];
dp[0][1] = -prices[0];
// 初始化第二次买入的状态是确保 最后结果是最多两次买卖的最大利润
dp[0][3] = -prices[0];

for (int i = 1; i < len; i++) {
dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
dp[i][2] = Math.max(dp[i - 1][2], dp[i][1] + prices[i]);
dp[i][3] = Math.max(dp[i - 1][3], dp[i][2] - prices[i]);
dp[i][4] = Math.max(dp[i - 1][4], dp[i][3] + prices[i]);
}

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

买卖股票的最佳时机IV

题目 :

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1: 输入:k = 2, prices = [2,4,1] 输出:2 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2。

示例 2: 输入:k = 2, prices = [3,2,6,5,0,3] 输出:7 解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4。随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。

提示:

  • 0 <= k <= 100
  • 0 <= prices.length <= 1000
  • 0 <= prices[i] <= 1000

思路

这道题的实现 和上一道题唯一的区别就是他把 2 换成了 k ,所以我们在定义状态的时候 需要定义 k 个状态。

使用二维数组 dp[i][j] :第i天的状态为j,所剩下的最大现金是dp[i][j]

j的状态表示为:

  • 0 表示不操作
  • 1 第一次买入
  • 2 第一次卖出
  • 3 第二次买入
  • 4 第二次卖出
  • …..

从上述的推导中 ,我们可以看出, 好像奇数次 都是在买入 。 偶数次都是在卖出。

那么根据这个规律 ,我们就可以推导出剩下的代码了

**初始化: ** 所有的卖入都初始化为 -prices[i]

1
2
3
for (int j = 1; j < 2 * k; j += 2) {
dp[0][j] = -prices[0];
}

**递推公式: **

1
2
3
4
5
6
for (int j = 0; j < 2 * k - 1; j += 2) {
//奇数次
dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
//偶数次
dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}

**推导结果: **

image-20230417194928332

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int maxProfit(int k, int[] prices) {
if (prices.length == 0) return 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];
}
}