题目描述
这道题目需要理解清楚。
知识点
动态规划的股票系列
我的实现
结果
码前思考
这道题目我想了挺久的,我是用类似于背包的思想去解题的。
定义一个二维数组dp[][]
,dp[i][j]
表示在前i
件物品的前提下,若【最后一步有效操作】是j
操作,能获得的最大收益。 这里的【有效操作】就是跳过了那些在某天不买卖股票的情况。
为什么会想到这么抽象的一个定义呢?
假设现在是第i
天,那么累积的【有效操作序列】,永远是以买
或者卖
或者冷却
结尾的。
如果在当天不进行某项操作,类似于背包问题的思想一样,只能说明当天进行某种操作是不值得的,前面的那些操作更加值得!
然后再是分解子问题,书写状态转移方程:
-
如果当天是
买
结尾的序列,那么买
可以是继承上一次买
,或者从上一次冷却
而来买
,抑或是之前从未操作过,现在第一次买
! -
如果当天是
卖
结尾的序列,那么卖
可以是继承上一次卖·
,或者是从上一次买
而来! -
如果当天是
冷却
结尾的序列,那么可以是继承了上一次冷却
,或者是从上一次卖
而来 。
代码实现
//采用背包的思想解题
//dp[i,j]表示在有前i件物品的前提下,若【最后一步】是j操作,能获得的最大收益
class Solution {
public:
int maxProfit(vector<int>& prices) {
int inf = 0x3fffffff;
//注意一共有3种操作
int len = prices.size();
vector<vector<int>> dp(len+1,vector<int>(3,-inf));
int ans = 0;
//开始进行递推
for(int j=1;j<=len;j++){
//如果是买的话,那么与前面的买和cooldown进行比较,还有只买自己(官方题解混杂了冷冻期和休息的概念,我感到不适,所以单独拿出只买自己的情况,其实冷冻期就是强制的一天休息罢了,所以还是从休息出发比较好)
dp[j][0] = max(dp[j-1][0],dp[j-1][2]-prices[j-1]);
dp[j][0] = max(dp[j][0],-prices[j-1]);
//如果是卖的话,那么与前面的卖和买做对比
dp[j][1] = max(dp[j-1][1],dp[j-1][0]+prices[j-1]);
//如果是cooldown,那么与前面的cooldown和卖做对比
dp[j][2] = max(dp[j-1][2],dp[j-1][1]);
}
//遍历得到最大值
for(int j=0;j<=2;j++){
ans = max(dp[len][j],ans);
}
return ans;
}
};
码后反思
- 我的思路主要是从01背包那边来的,因为我看见这种 O ( 3 n ) O(3^n) O(3n)的暴力解法时,已经习惯性地会使用背包来求解了;
- 第一次提交超时了,因为第一次提交时写的代码是枚举第一次买的位置,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)了,后来发现所谓的第一次买,其实是可以用下面这个代码内涵的:
//如果是买的话,那么与前面的买和cooldown进行比较,还有只买自己 dp[j][0] = max(dp[j-1][0],dp[j-1][2]-prices[j-1]); dp[j][0] = max(dp[j][0],-prices[j-1]);
网友超赞题解
DP 状态的选择
- 可以用一个三维数组,i 表示天,j表示是否持有股票,k表示是否是冷冻期
- 也可以用一个二维数组,dp[i][j]:i 表示天,j为 0,1,2:0表示持股,1表示不持股,2表示处于冷冻天
- 也可以用三个一维数组,分别代表第i天,3种选择:卖出、买进、休息,对应的最大收益。
- 也可以按手中是否持有股票,用两个一维数组,分别代表第 i 天,持有和没持有的最大收益
也就是说,在选择DP状态的定义时,可以尝试着降维。
状态转移
-
hold[i]
: 在第i
天的结束时,手中持有股票,此时的最大收益- 有股,有两种可能:今天休息、或买了股票
- 可能是昨天持有了,今天休息,也可能是前几天卖了,今天买的
hold[i] = Math.max(hold[i - 1], unhold[i - 2] - prices[i])
-
unhold[i]
: 第i
天的结束时,手中没有股票,此时的最大收益- 没股,有两种可能:今天休息、或卖了股票
- 可能是昨天也没持有,今天休息,也可能是昨天持有,今天卖了
unhold[i] = Math.max(unhold[i -1], hold[i - 1] + prices[i])
-
目标是求
unhold[n-1]
( n:0 1 2 3 … )
base case
hold[0] = -prices[0]
第0天买股票,收益-prices[0]
元hold[1] = Math.max(-prices[0], -prices[1])
第1天持有着股票,可能是昨天买的,今天休息,也可能是昨天休息,今天买的unhold[0] = 0
第0天没有持有股票,就是休息,收益 0 元
代码
时间复杂度:两个dp数组发生状态转移,花费 O ( 2 n ) O(2n) O(2n) ,总的来说是 O ( n ) O(n) O(n)
const maxProfit = (prices) => {
const n = prices.length; // n天
if (n == 0) return 0
let hold = new Array(n); // 第i天持有股票的最大收益
let unhold = new Array(n); // 第i天不持有股票的最大收益
hold[0] = -prices[0]; // 第0天 买了股票的收益
unhold[0] = 0
for (let i = 1; i < n; i++) {
if (i == 1) { // base case
hold[i] = Math.max(hold[i - 1], -prices[1]);
} else {
hold[i] = Math.max(hold[i - 1], unhold[i - 2] - prices[i]);
}
unhold[i] = Math.max(unhold[i - 1], hold[i - 1] + prices[i]);
}
return unhold[n - 1];
};
作者:hyj8
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/solution/dp-zhuang-tai-de-ding-yi-you-liang-chong-fang-fa-b/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
//所谓的冷冻期就是多了一天休息罢了,所以还是从休息出发理解
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len == 0){
return 0;
}
vector<int> hold(len); //hold表示当前持有股票
vector<int> unhold(len); //unhold表示当前不持有股票
//dp套路:初始化
hold[0] = -prices[0];
unhold[0] = 0;
for(int i=1;i<len;i++){
if(i==1){
hold[1] = max(hold[0],-prices[1]);//昨天持有,今天休息;前几天卖了,今天买入。
}else{
hold[i] = max(hold[i-1],unhold[i-2]-prices[i]);
}
unhold[i] = max(hold[i-1]+prices[i],unhold[i-1]);
}
return unhold[len-1];
}
};