Leetcode 122. 买卖股票的最佳时机
问题:给你一个整数数组 prices ,其中 prices [ i ] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。返回你能获得的最大利润 。
算法1:递归搜索 + 保存计算结果 = 记忆化搜索
每天可以有两种状态,持有股票或未持有股票,所对应的状态转移方程也不同,如下图。
从后向前递归,递归边界是第 0 天时,第 0 天不可能持有股票,但是递归过程中,会出现第 0 天的状态是 hold(持有)的情况,所以我们把这种情况的返回值设置成 INT_MIN ,这样就不会影响最后的输出。
递归入口是第 n 天时,由于第 n 天时未持有股票一定比持有股票利润大,所以递归入口状态设置成 false(未持有)。
代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<array<int,2>> memo(n,{-1,-1});
auto dfs = [&](auto &&dfs,int i,bool hold){
if(i < 0) return hold ? INT_MIN : 0; // INT_MIN 是因为第0天不可能持有股票,此时我们return一个最小值来保证其他递归的正确性
int& res = memo[i][hold];
if(res != -1) return res;// 之前被计算过了
if(hold) return res = max(dfs(dfs,i-1,true),dfs(dfs,i-1,false) - prices[i]); // 当天持有
return res = max(dfs(dfs,i-1,false),dfs(dfs,i-1,true) + prices[i]); // 当天未持有
};
return dfs(dfs,n-1,false); // 递归入口
}
};
算法2:1:1 翻译成递推
创建二维数组 dp ,并给 dp [ 0 ] [ 1 ] 赋初始值 INT_MIN ,遍历,列出状态转移方程。
代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<array<int,2>> dp(n + 1);
dp[0][1] = INT_MIN;
for(int i = 0;i < n;i++){
dp[i+1][0] = max(dp[i][0],dp[i][1] + prices[i]);
dp[i+1][1] = max(dp[i][1],dp[i][0] - prices[i]);
}
return dp[n][0];
}
};
算法3:空间优化
由 算法1、算法2 可知,我们在使用 dp 数组时只用到了相邻的两个格子,所以为了节省空间,我们可以不使用数组,仅使用变量来存储数据。dp0 表示未持有股票的利润,dp1 表示当前持有股票的利润。
代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int dp0 = 0,dp1 = INT_MIN;
for(int p : prices){
int new_dp0 = max(dp0,dp1 + p);
dp1 = max(dp1,dp0 - p);
dp0 = new_dp0;
}
return dp0;
}
};
算法4:
遍历数组,与标记到的最低价进行比较,画一个折线图,将所有的上升(红色)线段加起来即可得到答案。
代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size(),ans = 0;
for(int i = n - 1;i > 0;i--) prices[i] -= prices[i - 1];
prices[0] = 0;
for(int p : prices)
if(p > 0) ans += p;
return ans;
}
};