一、买卖股票最佳时机3
题目:
给定一个数组,它的第 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
思路:
首先明确dp数组的含义,本题中dp数组为dp[天数][情况](5),即为在第 i 天中,所得的最大利润为dp[][]
初始化dp数组
首先明确dp数组的各个阶段的含义
dp[i][0] 表达为第 i 天不做操作的情况(即不买入也不卖出)
dp[i][1] 表达为第 i 天第一次买入所得利润
dp[i][2] 表达为第 i 天第一次卖出所得利润
dp[i][3] 表达为第 i 天第二次买入所得利润
dp[i][4] 表达为第 i 天第二次卖出所得利润
对于第0天来说
dp[0][0] 不做任何操作因此基金为0
dp[0][1] 买入基金,利润为 -prices[0]
dp[0][2] 卖出基金,利润为 -prices[0]+prices[0]=0
dp[0][3] 再次买入基金,利润为 -prices[0]
dp[0][4] 再次卖出基金,利润为 -prices[0]+prices[0]=0
五种情况相当于在第0天重复进行买入卖出的操作
递推关系
对于第 i 天来说
dp[i][0] 只有前一天不操作的情况所得
因此 dp[i][0] = dp[i-1][0]
dp[i][1] 有两种情况,第一种是前一天已经第一次买入,今天未进行任何操作,即为dp[i-1][1],第二种是今天第一次买入,因此前一天是无操作的状态 即为dp[i-1][0]-prices[i],两种情况取利润最大值
因此 dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i])
dp[i][2] 有两种情况,第一种是前一天已经第一次卖出,今天未进行任何操作,即为dp[i-1][2],第二种是今天第一次卖出,因此前一天是第一次买入的状态 即为dp[i-1][1]+prices[i],两种情况取利润最大值
因此 dp[i][2] = max(dp[i-1][2],dp[i-1][1]+prices[i])
dp[i][3] 有两种情况,第一种是前一天已经第二次买入,今天未进行任何操作,即为dp[i-1][3],第二种是今天第二次买入,因此前一天是第一次卖出的状态 即为dp[i-1][2]-prices[i],两种情况取利润最大值
因此 dp[i][3] = max(dp[i-1][3],dp[i-1][2]-prices[i])
dp[i][4] 有两种情况,第一种是前一天已经第二次卖出,今天未进行任何操作,即为dp[i-1][4],第二种是今天第二次卖出,因此前一天是第二次买入的状态 即为dp[i-1][3]+prices[i],两种情况取利润最大值
因此 dp[i][4] = max(dp[i-1][4],dp[i-1][3]+prices[i])
总结dp数组的递推表达式为
dp[i][0] = dp[i - 1][0];
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 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]);
有了递推式,接下来代码就很简单了,遍历prices数组,寻找最大利润
代码:
public int maxProfit(int[] prices) {
int len = prices.length;
// 如果价格数组为空,则无法进行任何交易,利润为 0
if (len == 0)
return 0;
// dp[i][j] 表示第 i 天的状态,j 代表不同的状态:
// j = 0: 不持有股票,且还未完成第一次交易
// j = 1: 持有股票,且处于第一次买入状态
// j = 2: 不持有股票,且完成了第一次交易
// j = 3: 持有股票,且处于第二次买入状态
// j = 4: 不持有股票,且完成了第二次交易
int[][] dp = new int[len][5];
// 初始条件
// 第 0 天的状态初始化
dp[0][0] = 0; // 不持有股票,未交易,利润为 0
dp[0][1] = -prices[0]; // 持有股票,买入股票,利润为 -prices[0]
dp[0][2] = 0; // 不持有股票,完成第一次交易,利润为 0
dp[0][3] = Integer.MIN_VALUE; // 持有股票,买入股票时,无法买入(初始值为负无穷)
dp[0][4] = 0; // 不持有股票,完成第二次交易,利润为 0
// 从第 1 天到最后一天,更新动态规划数组
for (int i = 1; i < len; i++) {
dp[i][0] = dp[i - 1][0]; // 继续保持状态 0:不持有股票,未交易
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 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]); // 不持有股票且完成第二次交易:之前不持有或今天卖出股票
}
// 返回第 len-1 天完成两次交易后的最大利润
return dp[len - 1][4];
}
代码注释和解释
-
maxProfit
方法:- 参数:
int[] prices
是股票价格的数组。 - 功能:计算在最多进行两次交易的情况下,最大化利润。
- 步骤:
- 检查价格数组是否为空,若为空则返回 0。
- 初始化一个二维数组
dp
,其中dp[i][j]
表示第i
天的各种状态下的最大利润。 - 使用动态规划更新
dp
数组,从第 1 天开始,依据前一天的状态更新当前天的状态。 - 最终返回
dp[len - 1][4]
,即在最后一天完成两次交易的最大利润。
- 参数:
-
状态解释:
dp[i][0]
:在第i
天不持有股票,且未完成第一次交易。dp[i][1]
:在第i
天持有股票,且处于第一次买入状态。dp[i][2]
:在第i
天不持有股票,且完成了第一次交易。dp[i][3]
:在第i
天持有股票,且处于第二次买入状态。dp[i][4]
:在第i
天不持有股票,且完成了第二次交易。
动态规划转移方程
dp[i][0] = dp[i - 1][0]
:在第i
天保持不持有股票且未交易的状态。dp[i][1] = Math.max(dp[i - 1][1], dp[i - 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])
:选择卖出股票(完成第二次交易或维持卖出状态)。
二、买卖股票最佳时机4
题目:
给你一个整数数组 prices
和一个整数 k
,其中 prices[i]
是某支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k
笔交易。也就是说,你最多可以买 k
次,卖 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 。
思路:
与3不同的是,本题中可买卖的次数是不固定的,最多进行k次交易,因此需要一个参数 j 来随着k的值改变,而3中固定为两次买卖,对于dp[i][j]数组的含义与3中的相同,但递推公式略微改变
我们再次分析3中的递推关系
dp[i][0] = dp[i - 1][0];
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 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]);
可以发现,当 j 的值为奇数是为买入操作,j 为偶数时为卖出操作
因此在初始化dp数组时,可将第0天全部的dp[0][奇数j] 初始化为 -prices[0]
由于买入卖出操作是同时存在的,因此在判断 k 次买卖操作时必须将两种状态同步进行,因此在每次循环中对 j 进行 +2的操作,保证买入卖出的同步性
由3中的递推公式不难推出本题的递推公式
j 为奇数时,两种情况可能是前一天已经买入,或者是今天买入
dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
j 为偶数时,两种情况可能是前一天已经卖出,或者是今天卖出
dp[i][j + 2] = Math.max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
代码:
public int maxProfit(int k, int[] prices) {
int len = prices.length;
if (len == 0) return 0; // 如果价格数组为空,则无法进行交易,利润为 0
// dp[i][j] 表示第 i 天时的状态
// j = 0, 2, 4, ..., 2*k: 不持有股票的状态
// j = 1, 3, 5, ..., 2*k-1: 持有股票的状态
int[][] dp = new int[len][k * 2 + 1];
// 初始化第 0 天的状态
// dp[0][i] = -prices[0] 表示第 0 天买入股票后的利润
for (int i = 1; i < k * 2; i += 2) {
dp[0][i] = -prices[0]; // 第 0 天买入股票
}
// 更新 dp 数组
for (int i = 1; i < len; i++) {
for (int j = 0; j < k * 2 - 1; j += 2) {
// dp[i][j + 1] 表示第 i 天持有股票(买入)的状态
dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
// dp[i][j + 2] 表示第 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];
}
代码注释和解释
-
方法声明:
maxProfit(int k, int[] prices)
:计算最多k
次交易的情况下的最大利润。- 参数:
k
:允许的最大交易次数。prices
:股票价格数组。
-
边界条件:
if (len == 0)
:检查价格数组是否为空,如果为空则返回 0,因为没有股票可交易。
-
动态规划数组
dp
:dp[i][j]
:表示第i
天的状态,其中j
代表不同的状态。j
为偶数时表示不持有股票,为奇数时表示持有股票。- 状态解释:
j = 0, 2, 4, ..., 2*k
:不持有股票的状态。j = 1, 3, 5, ..., 2*k-1
:持有股票的状态。
-
初始化:
for (int i = 1; i < k * 2; i += 2)
:初始化第 0 天的状态,其中j
为奇数时的持有股票状态。因为在第 0 天买入股票的利润为-prices[0]
。
-
更新
dp
数组:- 外层循环
for (int i = 1; i < len; i++)
:遍历每一天的状态。 - 内层循环
for (int j = 0; j < k * 2 - 1; j += 2
:遍历每一个状态。dp[i][j + 1]
:表示第i
天持有股票的最大利润。可以是之前持有状态的最大值(不变),或之前没有持有的状态加上今天买入的价格。dp[i][j + 2]
:表示第i
天不持有股票的最大利润。可以是之前不持有状态的最大值(不变),或之前持有状态加上今天卖出的价格。
- 外层循环
-
返回结果:
return dp[len - 1][k * 2]
:返回在最后一天不持有股票时的最大利润,即完成所有可能的交易后的利润。
动态规划转移方程
-
持有股票的状态:
dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i])
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])
dp[i - 1][j + 2]
:保持不持有股票状态。dp[i - 1][j + 1] + prices[i]
:在之前的持有状态下卖出股票。
三、买卖股票最佳时机(冷冻期)
题目:
给定一个整数数组prices
,其中第 prices[i]
表示第 i
天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: prices = [1,2,3,0,2] 输出: 3 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
示例 2:
输入: prices = [1] 输出: 0
思路:
本题dp数组仍有4种状态,由于在卖出后的含有冷冻期,因此不持股状态有卖出和冷冻期后的状态,下面分别讨论其递推关系
首先明确dp数组各个状态的定义
dp[i][0] 表示持有股票
dp[i][1] 表示保持卖出股票的状态,即为冷冻期后面的状态,只要在冷冻期后一直没有买入股票则一直为保持卖出股票状态
dp[i][2] 表示卖出股票状态
dp[i][3] 表示冷冻期
对于dp[i][0],有三种情况,第一种那就是前一天已经持有股票,对于前一天买入股票有两种
其一就是前一天是保持卖出股票的状态,即为dp[i-1][1]-prices[i],另一个就是前一天是冷冻
期即为dp[i-1][3]-prices[i]
因此 dp[i][0] = max(dp[i-1][0],dp[i-1][1]-prices[i],dp[i-1][3]-prices[i]
对于dp[i][1],有两种情况,第一种是前一天也为保持卖出股票的状态,另一种是前一天为冷冻期
因此 dp[i][1] = max(dp[i-1][1],dp[i-1][3])
对于dp[i][2],前一天一定是持有股票的状态
因此 dp[i][2] = dp[i-1][0]+prices[i]
对于dp[i][3],前一天一定是卖出股票状态
因此 dp[i][3] = dp[i-1][2]
得出最终dp数组递推公式为
dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]));
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][3]);
dp[i][2] = dp[i - 1][0] + prices[i];
dp[i][3] = dp[i - 1][2];
代码:
public int maxProfit(int[] prices) {
int n = prices.length; // 获取价格数组的长度
if (n == 0) return 0; // 如果没有价格数据,返回0利润
// dp[i][0]: 在第i天持有股票的最大利润
// dp[i][1]: 在第i天处于卖出股票后的状态的最大利润
// dp[i][2]: 在第i天卖出股票后的状态的最大利润
// dp[i][3]: 在第i天处于冷冻期的状态的最大利润
int[][] dp = new int[n][4]; // 创建一个二维数组用于存储状态
dp[0][0] = -prices[0]; // 初始状态,买入股票,持有股票时利润为 -prices[0]
for (int i = 1; i < n; i++) {
// dp[i][0]: 在第i天持有股票的最大利润
dp[i][0] = Math.max(dp[i - 1][0], // 保持前一天持有股票的状态
Math.max(dp[i - 1][3] - prices[i], // 从冷冻期转移到持有股票状态
dp[i - 1][1] - prices[i])); // 从卖出股票后的状态转移到持有股票状态
// dp[i][1]: 在第i天保持卖出股票后的状态的最大利润
dp[i][1] = Math.max(dp[i - 1][1], // 保持前一天的卖出状态
dp[i - 1][3]); // 从冷冻期转移到卖出股票后的状态
// dp[i][2]: 在第i天卖出股票后的状态的最大利润
dp[i][2] = dp[i - 1][0] + prices[i]; // 从前一天持有股票状态卖出股票
// dp[i][3]: 在第i天处于冷冻期的状态的最大利润
dp[i][3] = dp[i - 1][2]; // 从前一天的卖出状态转移到冷冻期状态
}
// 返回最大利润,可能在冷冻期、卖出股票后的状态或者卖出股票状态
return Math.max(dp[n - 1][3], Math.max(dp[n - 1][1], dp[n - 1][2]));
}
状态转移方程解释
-
dp[i][0]
: 在第i
天持有股票的最大利润dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]))
- 解释:
dp[i - 1][0]
: 保持前一天持有股票的状态。dp[i - 1][3] - prices[i]
: 从冷冻期状态转移到持有股票状态,即前一天处于冷冻期的最大利润减去今天的股票价格。dp[i - 1][1] - prices[i]
: 从卖出股票后的状态转移到持有股票状态,即前一天卖出后的最大利润减去今天的股票价格。
-
dp[i][1]
: 在第i
天处于卖出股票后的状态的最大利润dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][3])
- 解释:
dp[i - 1][1]
: 保持前一天的卖出状态。dp[i - 1][3]
: 从冷冻期状态转移到卖出股票后的状态,即前一天的冷冻期状态的最大利润。
-
dp[i][2]
: 在第i
天卖出股票后的状态的最大利润dp[i][2] = dp[i - 1][0] + prices[i]
- 解释:
dp[i - 1][0] + prices[i]
: 从前一天持有股票状态卖出股票,获得今天的股票价格。
-
dp[i][3]
: 在第i
天处于冷冻期的状态的最大利润dp[i][3] = dp[i - 1][2]
- 解释:
dp[i - 1][2]
: 从前一天的卖出状态转移到冷冻期状态,即前一天卖出后的最大利润。
四、买卖股票最佳时期(含手续费)
题目:
给定一个整数数组 prices
,其中 prices[i]
表示第 i
天的股票价格 ;整数 fee
代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
示例 1:
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2 输出:8 解释:能够达到的最大利润: 在此处买入 prices[0] = 1 在此处卖出 prices[3] = 8 在此处买入 prices[4] = 4 在此处卖出 prices[5] = 9 总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8
示例 2:
输入:prices = [1,3,7,5,10,3], fee = 3 输出:6
思路:
首先明确dp数组的含义,本题中dp数组共有两种状态,分别是买入卖出状态,其状态转移方程大致类似,只需要在卖出股票时减去手续费即可
递归方程
dp[i][0] 表示第 i 天持有股票,情况有两种,前一天持有股票或者前一天卖出股票
dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] - prices[i]);
dp[i][1] 表示第 i 天卖出股票,情况有两种,前一天卖出股票或者前一天买入股票且再次卖出
dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] + prices[i] - fee);
代码:
public int maxProfit(int[] prices, int fee) {
int n = prices.length; // 获取价格数组的长度
if (prices == null || n == 0)
return 0; // 如果价格数组为空或长度为0,返回0利润
int[][] dp = new int[n][2]; // 创建二维数组,用于存储状态
dp[0][0] = -prices[0]; // 第0天持有股票的最大利润为负的第一天股票价格(即买入股票)
for (int i = 1; i < n; i++) {
// dp[i][0]: 在第i天持有股票的最大利润
dp[i][0] = Math.max(dp[i - 1][0], // 保持前一天持有股票的状态
dp[i - 1][1] - prices[i]); // 从前一天卖出状态转移到持有股票状态
// dp[i][1]: 在第i天不持有股票(即卖出股票后的状态)的最大利润
dp[i][1] = Math.max(dp[i - 1][1], // 保持前一天不持有股票的状态
dp[i - 1][0] + prices[i] - fee); // 从前一天持有股票状态卖出股票并扣除手续费
}
// 返回在最后一天最大利润,可能是持有股票状态或不持有股票状态
return Math.max(dp[n - 1][0], dp[n - 1][1]);
}
状态转移方程解释
-
dp[i][0]
: 在第i
天持有股票的最大利润dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i])
- 解释:
dp[i - 1][0]
: 保持前一天持有股票的状态,即在第i-1
天持有股票的利润。dp[i - 1][1] - prices[i]
: 从第i-1
天卖出股票的状态转移到第i
天持有股票状态,这里减去当天股票价格prices[i]
,表示用之前卖出股票后的最大利润再购买股票。
-
dp[i][1]
: 在第i
天不持有股票(即卖出股票后的状态)的最大利润dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee)
- 解释:
dp[i - 1][1]
: 保持前一天不持有股票的状态,即在第i-1
天不持有股票的利润。dp[i - 1][0] + prices[i] - fee
: 从第i-1
天持有股票状态转移到第i
天卖出股票的状态。这里加上今天股票价格prices[i]
并减去手续费fee
,表示卖出股票并扣除手续费后的利润。
今天的学习就到这里
相关资料: