#LeetCode121. Best Time to Buy and Sell Stock
#LeetCode121. 视频讲解:动态规划之 LeetCode:121.买卖股票的最佳时机1_哔哩哔哩_bilibili
动态规划五部曲:
1. dp数组的含义:dp[i][0] 表示第i 支股票持有时的最大金额,dp[i][1] 表示第i 支股票不持有时的最大金额。这个持有与否是代表的一种状态而不是一定在这个时候买入或卖出。如果在第2 支股票时买入,那么在第3、4、5都是持有的状态
2. 递推公式:如果是持有股票的状态,有可能是之前已经买入,那么就是dp[i - 1][0] ,如果是这次买入则为-prices[i] ,如果是当前不持有股票,有可能是之前已经卖出,那么就是dp[i - 1][1],或者这次卖出dp[i - 1][0] + prices[i] ,是当前的价格加上之前持有股票的最大现金
3. dp数组初始化:dp[i][0] = -prices[i] 是买入第一支股票的金额,是负值,因为起始的金额为0, dp[0][1] = 0 是不持有股票的状态,则为0
4. 遍历顺序:正序遍历
5. 打印dp 数组:打印方便理解,如果把例子可视化可以看出二维数组是为了记录买入的最低价格以及记录卖出的最高价格:
例子[7, 1, 5, 3, 6, 4]
dp[0][0] = -7; dp[0][1] = 0;
dp[1][0] = max(-7, -1) = -1; dp[1][1] = max(0, -7 + 1) = 0;
dp[2][0] = max(-1, -5) = -1; dp[2][1] = max(0, -1 + 5) = 4;
dp[3][0] = max(-1, -3) = -1; dp[3][1] = max(4, -1 + 3) = 4;
dp[4][0] = max(-1, -6) = -1; dp[4][1] = max(4, -1 + 6) = 5;
dp[5][0] = max(-1, -4) = -1; dp[5][1] = max(5, -1 + 4) = 5;
代码:
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][2];
dp[0][0] = -prices[0]; // have
dp[0][1] = 0; // have not
for (int i = 1; i < prices.length; 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[prices.length - 1][1];
}
}
#LeetCode122. Best Time to Buy and Sell Stock II
#LeetCode122. 视频讲解:动态规划,股票问题第二弹 | LeetCode:122.买卖股票的最佳时机II_哔哩哔哩_bilibili
与第一个股票买卖问题的区别是,之前只允许买卖一次,这个题目是允许多次买卖的。
如果使用贪心算法,那么只需要考虑只要第二天比前一天价格高就卖出。
在动态规划算法中,卖出的情况是相同的,但买入的情况不同。区别在于之前是只有一次机会买入卖出,所以价格是0 - prices[i],现在有了之前买入卖出赚的差额,不再是之前的0 了,所以dp[i][0] 有不同。
动态规划代码:
class Solution {
public int maxProfit(int[] prices) {
// Dynamic Programming
int[][] dp = new int[prices.length][2];
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.length; 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[prices.length - 1][1];
}
}
#LeetCode123. Best Time to Buy and Sell Stock III
#LeetCode122. 视频讲解:动态规划,股票至多买卖两次,怎么求? | LeetCode:123.买卖股票最佳时机III_哔哩哔哩_bilibili
与前两题不同的是,这个题目只允许买卖两次,可以创建一个更大的数组来分别记录不同状态的金额。
动态规划五部曲:
1. dp数组的含义:dp[i][0] 表示最初没有持有过股票时的状态,dp[i][1] 表示第i 支股票持有时(第一次持有)的最大金额,dp[i][2] 表示第i 支股票不持有时(第一次不持有)的最大金额,dp[i][3] 表示第i 支股票持有时(第二次持有)的最大金额,dp[i][4] 表示第i 支股票不持有时(第二次不持有)的最大金额
2. 递推公式:
第一次持有dp[i][1] :有可能是之前已经买入,那么就是dp[i - 1][1] ,如果是这天刚买入则为dp[i - 1][0] - prices[i] (其实就是-prices[i] )
第一次不持有dp[i][2] :有可能是之前已经卖出,那么就是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 - 1][2] 代表第一次卖出后结余的钱)
第二次不持有dp[i][4] :有可能是之前已经卖出,那么就是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. 遍历顺序:正序遍历,因为dp[i] 依赖于dp[i - 1] 的状态
5. 打印dp 数组:打印方便理解
最后的结果可以不取最大值,因为第二次的当天买入卖出也是可以的(收支平衡),依然是最后的数组代表最大金额。
代码:
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][5];
dp[0][0] = 0;
dp[0][1] = -prices[0]; // buy
dp[0][2] = 0; // buy and sell
dp[0][3] = -prices[0]; // buy
dp[0][4] = 0; // buy and sell
for (int i = 1; i < prices.length; 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]);
}
return dp[prices.length - 1][4];
}
}