所用代码 java
买卖股票的最佳时机喊冻结期
题目链接:买卖股票的最佳时机喊冻结期 - 中等
思路
- dp数组:dp[i] [j],第i天状态为j,最大利润dp[i] [j]
- dp[i] [0]:持有股票的最大利润
- dp[i] [1]:保持卖出股票的状态
- dp[i] [2]:具体卖出股票的状态 – 第二天就是冷冻期
- dp[i] [3]:冷冻期
- 递推公式:
dp[i][0] = max(dp[i-1][0], dp[i-1][3] - price[i], dp[i-1][1]-price[i])
- 持有三个状态:前一天持有股票、冷冻期买入、保持卖出时卖出
dp[i][1] = max(dp[i-1][1], dp[i-1][3])
- 保持卖出两个状态:前一天保持卖出、冷冻期
dp[i][2] = dp[i-1][0] + price[i]
- 卖出股票即前一天一定是持有股票
dp[i][3] = dp[i-1][2]
- 冷冻期前一天一定是卖出股票状态
- 初始化:
dp[0][0] = -price[0]
持有股票包含当天买入dp[0][1] = 0
非负状态,无实意,通过递推dp[1][0]=dp[0][1]-price[1]
公式发现需要0dp[0][2] = 0
和上面一样,但也可以理解当天买当天卖dp[0][3] = 0
同理
- 遍历顺序
- 打印
- 返回 max(dp[0] [1], dp[0] [2], dp[0] [3]) 卖出三种状态谁赚的多
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][4];
// 初始化 -- 四个状态
dp[0][0] = -prices[0]; // 1、持有
dp[0][1] = 0; // 2、保持卖出
dp[0][2] = 0; // 3、具体卖出,第二天为冷冻期
dp[0][3] = 0; // 4、冷冻期
for (int i = 1; i < len; i++) {
// 1、持有(前一天持有、前一天保持卖出今日买入,前一天冷冻期今日买入)
dp[i][0] = Math.max(dp[i-1][0], Math.max(dp[i-1][1] - prices[i], dp[i-1][3] - prices[i]));
// 2、保持卖出(前一天保持卖出、冷冻期(因为冷冻期第二天没法交易))
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3]);
// 3、具体卖出(前一天肯定是持有,然后今日卖出)
dp[i][2] = dp[i-1][0] + prices[i];
// 4、冷冻期(前一天肯定是具体卖出)
dp[i][3] = dp[i-1][2];
}
// 3种卖出状态最大即为利润最大
return Math.max(dp[len-1][1], Math.max(dp[len-1][2], dp[len-1][3]));
}
}
由于所有状态都由前一天的状态推导出来的,所以可以优化为一维的数组以优化空间:
class Solution {
public int maxProfit(int[] prices) {
int[] dp = new int[4];
// 初始化 -- 四个状态
dp[0] = -prices[0]; // 1、持有
dp[1] = 0; // 2、保持卖出
dp[2] = 0; // 3、具体卖出,第二天为冷冻期
dp[3] = 0; // 4、冷冻期
for (int i = 1; i < len; i++) {
// 由于前一天的dp[0]dp[2]都会发生改变,所以需要保存临时变量
int temp1 = dp[0];
int temp2 = dp[2];
// 1、持有(前一天持有、前一天保持卖出今日买入,前一天冷冻期今日买入)
dp[0] = Math.max(dp[0], Math.max(dp[1] - prices[i], dp[3] - prices[i]));
// 2、保持卖出(前一天保持卖出、冷冻期(因为冷冻期第二天没法交易))
dp[1] = Math.max(dp[1], dp[3]);
// 3、具体卖出(前一天肯定是持有,然后今日卖出)
dp[2] = temp1 + prices[i];
// 4、冷冻期(前一天肯定是具体卖出)
dp[3] = temp2;
}
// 3种卖出状态最大即为利润最大
return Math.max(dp[1], Math.max(dp[2], dp[3]));
}
}
总结
其实卖出本来就分为两个状态,一是当天就是卖出,二是非当天卖出一直保持卖出的状态,而在前面的题中由于这两者没区别,所以没有区分,这题有了一个冷冻期,非当天卖出则会影响第二天能否买入。
另外,其实我们也可以优化成3个状态:
- dp[i] [0]:买入,需在冷冻后一天才能买入
- dp[i] [1]:卖出
- dp[i] [2]:冷冻期,最大利润为前一天卖出
递推公式:
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2] - prices[i]);
- 买入两个状态:前一天买入、卖出(冷冻期后一天才能买)
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);
- 卖出两个状态:前一天保持卖出,今天卖出
dp[i][2] = dp[i-1][1];
- 冷冻期一个状态:前一天卖出
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][3];
// 初始化 -- 四个状态
dp[0][0] = -prices[0]; // 1、买入, 冷冻期的后一天才可以买入
dp[0][1] = 0; // 2、卖出
dp[0][2] = 0; // 3、冷冻期
for (int i = 1; i < len; i++) {
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2] - prices[i]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);
dp[i][2] = dp[i-1][1];
}
return Math.max(dp[len-1][1], dp[len-1][2]);
}
}
上面也可压缩一维数组。
还有一种思维,和之前的买卖股票3的思想类似,就是把买卖分为一组。
对于不持有(卖出):上一次买卖无影响
对于持有(买入):由于上一次卖出会影响本次买入,所以得去考虑上上次的卖出
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) return 0; // 只有一个数利润为0
int[][] dp = new int[len][2];
// 初始化 -
dp[0][0] = 0; // 第一次不持有
dp[0][1] = -prices[0]; // 第一次持有
dp[1][0] = Math.max(dp[0][0], dp[0][1] + prices[1]); // 第二次不持有(卖出)
dp[1][1] = Math.max(dp[0][1], -prices[1]); // 第二次持有(买入)
for (int i = 2; i < len; i++) {
// 第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-2][0] - prices[i]);
}
return dp[len-1][0];
}
}
另外:还有一种思维,不好理解。
dp数组的含义为:
- dp[i] [0]:第i天持有股票的收益
- dp[i] [1]:第i天不持有股票的收益
递推公式:
dp[i][0] = max(dp[i-1][0], dp[i-2][1] + price[i-1])
dp[i-1][0]
前一天持有- 前一天不持有有两个状态
- 第i天为冷静期,所以不能以i-1天购买股票,只能在i-2天购买股票
- 第i天不为冷静期,证明在i-1天没有进行卖出,所以可以在i-2天进行买入
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + price[i-1])
- 不持有只有两种情况:前一天不持有,或者前一天持有今日卖出
price[i-1]
是因为前一天持有的状态不管是不是冷静期,都不能在当天进行操作,只能在前一天操作。
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len+1][2];
// 初始化 -- 2个状态
dp[1][0] = -prices[0];
for (int i = 2; i <= len; i++) {
dp[i][0] = Math.max(dp[i-1][0], dp[i-2][1] - prices[i-1]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i-1]);
}
return dp[len][1];
}
}
买卖股票的最佳时机含手续费 LeetCode 714
题目链接:买卖股票的最佳时机含手续费 LeetCode 714 - 中等
思路
本题贪心做过,贪心那个思路有点难想,用动态规划简单一点,就是买卖股票2的情况下,卖股票多扣一次手续费,具体可以看之前的买卖股票2链接:买卖股票2.
我们的每个状态都可以由前一个状态得出,这里我直接用一维数组。
- dp数组
- dp[0] 持有股票的最大利润
- dp[1] 不持有
- 递推公式:
dp[0] = max(dp[0], dp[1] - price[i])
- 持有股票:上一次持有、上次不持有,本次买入
dp[1] = max(dp[1], dp[0] + price[i] - fee)
- 不持有股票:上一次不持有、 上一次持有,本次卖出,还要被扣手续费
- 初始化:
dp[0] = -price[0]
首次持有,买入dp[1] = 0
无意义,不能是 -fee,因为第一天根本没有买卖。
class Solution {
public int maxProfit(int[] prices, int fee) {
int[] dp = new int[2];
// 初始化
dp[0] = -prices[0]; // 持有
dp[1] = 0; // 不持有
for (int i = 1; i < prices.length; i++) {
dp[0] = Math.max(dp[0], dp[1] - prices[i]);
dp[1] = Math.max(dp[1], dp[0] + prices[i] - fee);
// System.out.println("dp[0] = "+dp[0] + "\tdp[1] = " +dp[1]);
}
return dp[1];
}
}
总结
买卖股票系列已完结,买卖股票通常来说有两个状态:买、卖:
-
买卖股票1:买卖一次。买的时候就只能是price[i]的最小值
-
买卖股票2:**买卖多次。**买可有上次卖出的利润来买dp[i-1] [1] - price[i].
链接:买卖股票1、2
-
买卖股票3:**最多买卖两次。**买两个状态(第一次买,第二次买),卖两个状态(第二次卖,第二次卖),还可以拆分一个dp[0] [0]表示无状态
-
买卖股票4:最多买卖k次。由买卖3而来,通过3得到规律,并且使用dp[i] [j+1]表买,使用dp[i] [j+2]表示卖,循环的时候我们一组一组的进行循环(j+=2),找到每组买卖的最大利润
链接:买卖股票3、4
-
买卖股票(含冷冻期):买卖多次,卖出有一天冷冻期。把卖出拆分为三个状态(之前卖出今日为卖出状态、今日卖出、昨日卖出今日冷冻期)
-
买卖股票(含手续费):买卖多次,卖出需手续费。就是在买卖2的基础上,卖的时候多扣一个手续费。