欢迎指正
参考来源: labuladong的算法小抄
墙裂推荐上面那个大佬的算法文章。
基础状态转移方程:(根据需要进行修改)
base case:
dp[-1][k][0] = dp[i][0][0] = 0
dp[-1][k][1] = dp[i][0][1] = -infinity
// 状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
// 之所以 k - 1,是因为,之前以及买入过了,所以买入的次数就要-1
// 理解一下就是:一次交易由(买-卖)构成,k次交易就有 (k次买-k次卖)有点类似于左右括号
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
// dp[3][2][1] 的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易
// dp[3][2][0] 的含义就是:今天是第三天,我现在手上没持有着股票,至今最多进行 2 次交易
// 我们要求的就是 dp[n - 1][k][0]:今天是最后一天,至今最多进行2次交易,我的最大获利
说明:下面的优化之所以能够进行,是因为在这几个题目中,状态转移只在相邻的两个状态间发生,所以可以使用一个变量存储前后两个状态值,然后进行更新就可以了。
121. 买卖股票的最佳时机
1. 解法一:动态规划(带哈希表)
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
int[][] dp = new int[n][2];
for (int i = 0;i < n;i ++) {
// base contidition
if (i - 1 == -1) {
// 第 0 天不操作
dp[i][0] = 0;
// 第 0 天买入
dp[i][1] = -prices[i];
continue;
}
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
// 因为交易次数只能1次,前面已经买过了,所以不能再买
dp[i][1] = Math.max(dp[i - 1][1], - prices[i]);
}
// 最后一天不持有的利润最大
return dp[n - 1][0];
}
}
2. 解法二:优化空间复杂度到 O(1)
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
int dp_i_0 = 0, dp_i_1 = -prices[0];
for (int i = 1;i < n;i ++) {
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = Math.max(dp_i_1, -prices[i]);
}
return dp_i_0;
}
}
122. 买卖股票的最佳时机 II
不限制交易次数,即
k = infinity
,则k - 1 = k
1. 解法一:动态规划(带哈希表)
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
int[][] dp = new int[n][2];
for (int i = 0;i < n;i ++) {
// base condition
if (i == 0) {
dp[i][0] = 0;
dp[i][1] = -prices[i];
continue;
}
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][0];
}
}
2. 解法二:优化空间复杂度到 O(1)
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
int dp_i_0 = 0, dp_i_1 = -prices[0];
for (int i = 1;i < n;i ++) {
// 因为 dp_i_1 需要的是更新前的 dp_i_0,所以需要先保存起来
int temp = dp_i_0;
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = Math.max(dp_i_1, temp - prices[i]);
}
return dp_i_0;
}
}
123. 买卖股票的最佳时机 III
原始的动态转移方程,没有可化简的地方
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
1. 解法一:动态规划(带哈希表)
因为不只与前一个状态相关,所以想优化空间复杂度不好实现(我不会了)
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
// 最多完成 2 笔交易
int max_k = 2;
int[][][] dp = new int[n][max_k + 1][2];
for (int i = 0;i < n;i ++) {
// 因为要考虑到最多交易数为2,所以需要遍历一遍
for (int k = max_k; k >= 1;k --) {
// base condition
if (i == 0) {
dp[i][k][0] = 0;
dp[i][k][1] = -prices[i];
continue;
}
dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
}
}
return dp[n - 1][max_k][0];
}
}
188. 买卖股票的最佳时机 IV
1. 解法一:动态规划(带哈希表)——> 超出内存限制
class Solution {
public int maxProfit(int k, int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
int[][][] dp = new int[n][k + 1][2];
for (int i = 0;i < n;i ++) {
for (int j = k;j >= 1;j --) {
// base condition
if (i == 0) {
dp[i][j][0] = 0;
dp[i][j][1] = -prices[i];
continue;
}
dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
}
}
return dp[n - 1][k][0];
}
}
2. 解法二:进行优化
一次交易由买入和卖出构成,至少需要两天。所以说有效的限制
k
应该不超过n/2
,如果超过,就没有约束作用了,相当于k = +infinity
。这种情况见 122
class Solution {
public int maxProfit(int k, int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
if (k > n / 2) {
return maxProfit_infinity(prices);
}
int[][][] dp = new int[n][k + 1][2];
for (int i = 0;i < n;i ++) {
for (int j = k;j >= 1;j --) {
// base condition
if (i == 0) {
dp[i][j][0] = 0;
dp[i][j][1] = -prices[i];
continue;
}
dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
}
}
return dp[n - 1][k][0];
}
// 这一部分的代码,其实就是复用了 122 的代码
private int maxProfit_infinity(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][2];
for (int i = 0;i < n;i ++) {
if (i == 0) {
dp[i][0] = 0;
dp[i][1] = -prices[i];
continue;
}
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][0];
}
}
309. 最佳买卖股票时机含冷冻期
1. 解法一:动态规划(带哈希表)
注意:因为带来冷冻期,所以状态转移方程有了一定变化
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
// 解释:第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1 。中间经历一个冷冻期 i - 1
dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i])
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
// 多次买卖,所以 k = infinity
int[][] dp = new int[n][2];
for (int i = 0;i < n;i ++) {
// base condition
if (i == 0) {
dp[i][0] = 0;
dp[i][1] = -prices[i];
continue;
}
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
// 要考虑从 i - 2 开始转移,所以需要提前判断 i 和 2 的大小关系
if (i == 1) {
dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
} else
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 2][0] - prices[i]);
}
return dp[n - 1][0];
}
}
2. 解法二:优化空间复杂度到 O(1)
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
int dp_i_0 = 0;
int dp_i_1 = -prices[0];
// 记前两天不持有的利润即:dp[i - 2][0]
int dp_pre_0 = 0;
for (int i = 1;i < n;i ++) {
int temp = dp_i_0;
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]);
// 往后遍历的时候,需要时刻更新这个 dp_pre_0,即dp[i - 2][0]
dp_pre_0 = temp;
}
return dp_i_0;
}
}
714. 买卖股票的最佳时机含手续费
本质上来说与122 无限购买次数相同,我们只需要在卖出的同时扣除手续费即可
1. 解法一:动态规划(带哈希表)
class Solution {
public int maxProfit(int[] prices, int fee) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
int[][] dp = new int[n][2];
for (int i = 0;i < n;i ++) {
if (i == 0) {
dp[i][0] = 0;
dp[i][1] = -prices[i];
continue;
}
// 在卖出的时候扣除手续费即可,在买入的时候扣除也是可以的
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[n - 1][0];
}
}
2. 解法二:优化空间复杂度到 O(1)
class Solution {
public int maxProfit(int[] prices, int fee) {
if (prices == null || prices.length <= 1) return 0;
int n = prices.length;
int dp_i_0 = 0;
int dp_i_1 = -prices[0];
for (int i = 1;i < n;i ++) {
int temp = dp_i_0;
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i] - fee);
dp_i_1 = Math.max(dp_i_1, temp - prices[i]);
}
return dp_i_0;
}
}