leetcode:121. 买卖股票的最佳时机

题目

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例:

  1. 输入:[7,1,5,3,6,4]
  2. 输出:5
  3. 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5
  4. 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
  1. 输入:prices = [7,6,4,3,1]
  2. 输出:0
  3. 解释:在这种情况下, 没有交易完成, 所以最大利润为 0

解答 & 代码

动态规划(此类题型通用框架)

  • 动态规划数组 **dp[i][k][0/1]**:代表 今天是第 i 天,至今最多进行 k 次交易,今天(最终)手上不持有/持有股票 的最大利润
    • i 代表今天是第几天,0<=i<=n-1
    • k 代表至今最多允许进行的交易次数,1<=k<=K
    • 最后一位是持有状态, 0 代表今天最终没有持有股票,1 代表今天最终持有股票
    • 题目最终要求的最大利润就是 **dp[n-1][K][0]**本题中 **K=1**,即求的是 **dp[n-1][1][0]**
      • n 是天数(即 prices 数组长度)
      • K 是允许的交易次数上限
        • 题目最终要求的最大利润是 dp[n-1][K][0],而不是 dp[n-1][K][1],因为后者代表最后一天手上还持有股票,而前者代表最后一天手上没有股票了(没有交易过或已经卖了,至少不会为负数,不会亏),因此前者显然大于后者

1.png

  • 状态转移方程(参照上图的状态机):
    • **dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])**
      • dp[i-1][k][0] 代表今天不交易(rest)
      • dp[i-1][k][1] + prices[i] 代表今天卖出股票(sell)
    • **dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])**
      • dp[i-1][k][1] 代表今天不交易(rest)
      • dp[i-1][k-1][0] - prices[i] 代表今天买入股票(buy)
  • 初始化
    • dp[-1][...][0] = 0:第 -1 天还没开始(天数 i 从 0 开始计),因此最大利润为 0
    • dp[-1][...][1] = -∞:还没开始,不可能持有股票,因此设为负无穷,使得状态转移方程中取 max 时抛弃这个选项
    • dp[...][0][0] = 0:k=0 说明不允许交易,因此最大利润为 0
    • dp[...][0][1] = -∞:k=0 代表不允许交易,因此不可能持有股票,因此设为负无穷,使得状态转移方程中取 max 时抛弃这个选项

本题解法

  • 本题中 K=1,即最终求的是 **dp[n-1][1][0]**
  • 状态转移方程:
    • **dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])**
    • **dp[i][1][1] = **max(dp[i-1][1][1], dp[i-1][0][0] - prices[i])** = max(dp[i-1][1][1], -prices[i])**
      • 上面“初始化”部分已经解释过,k=0 代表不允许交易,因此最大利润为 0,因此 dp[i-1][0][0]=0
  • 初始化:dp[-1][1][0] = 0dp[-1][1][1] = -∞

本题中状态转移方程新的状态之和前一天的状态有关,因此不需要数组,直接存储前一天的状态即可

  1. class Solution {
  2. public:
  3. int maxProfit(vector<int>& prices) {
  4. // 初始化
  5. int dp_i_0 = 0; // 即 dp[i][1][0]
  6. int dp_i_1 = INT_MIN; // 即 dp[i][1][1]
  7. // 状态转移
  8. for(int i = 0; i < prices.size(); ++i)
  9. {
  10. // 今天(第 i 天)最终不持有股票的最大利润,取决于以下两者的最大值
  11. // - dp_i_0:昨天(第 i-1 天)就没有持有股票,今天休息不操作
  12. // - dp_i_1 + prices[i]:昨天(第 i-1 天)持有股票,今天卖掉
  13. dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
  14. // 今天(第 i 天)最终持有股票的最大利润,取决于以下两者的最大值
  15. // - dp_i_1:昨天(第 i-1 天)就持有股票,今天休息不操作
  16. // - -prices[i]:昨天(第 i-1 天)没有持有股票,今天买入
  17. dp_i_1 = max(dp_i_1, -prices[i]);
  18. }
  19. return dp_i_0;
  20. }
  21. };

复杂度分析:设 prices 数组长度为 N

  • 时间复杂度 O(N)
  • 空间复杂度 O(1)

执行结果:

  1. 执行结果:通过
  2. 执行用时:92 ms, 在所有 C++ 提交中击败了 88.94% 的用户
  3. 内存消耗:91.1 MB, 在所有 C++ 提交中击败了 78.36% 的用户

非模板式解法:
[容易] 121. 买卖股票的最佳时机