第零章:一个方法团灭LEETCODE股票买卖问题

labuladong一个方法团灭LEETCODE股票买卖问题用的状态机,也就是三维的dp数组(天数,是否处于持有股票的状态(用0和1表示),已经交易的次数)。根据题意不断改变状态转移方程。
本文采用二维数组,把是否持有股票的状态用不同的dp数组(buy,sell)表示。

解题框架来自2 solutions, 2 states DP solutions, clear explanation!

121. 买卖股票的最佳时机

动态规划:

  • dp[i]数组的含义:前i天的最大收益
    • 当天不卖:dp[i]=dp[i-1]
    • 当天卖:dp[i]=prices[i]-minPrice
  • 每到新的一天,就更新从开始到当天为止的最低价格
class Solution {
public:
    int maxProfit(vector<int> &prices) {
        vector<int> dp(prices.size(), 0);
        int minPrice=prices[0];
        for (int i = 1; i < prices.size(); ++i) {
            minPrice=min(minPrice,prices[i]);// 更新最低价
            dp[i]=max(dp[i-1],prices[i]-minPrice);
        }
        return dp.back();
    }
};

以下的题目中,当天结束后可能有持有、不持有(能买,不能卖(因为是冷冻期))的状态。因此用到2~3个dp数组来存储状态

122. 买卖股票的最佳时机 II

动态规划解法

不管这个人当天买还是卖还是不操作,他当天结束后最终只有两种状态:持有股票和不持有股票

  • vector<int>buy(n,0); buy[i]表示第i天处于持有股票的状态(即:处于的状态)时的最大收益。第i天持有股票是因为有两种情况:
    • 前一天也处于持有状态,今天没任何操作:buy[i-1]
    • 前一天处于不持有股票状态,今天买了:sell[i-1]-prices[i]
  • vector<int>sell(n,0);sell[i]表示第i天处于不持有股票的状态(即:处于的状态)时的最大收益。第i天不持有股票是因为有两种情况:
    • 前一天也处于不持有状态,今天没任何操作:sell[i-1]
    • 前一天处于持有股票状态,今天卖了:buy[i-1]+prices[i]
  • base case: 第0天时若持有股票(说明买了第0天的股票),则buy[0](表示第0天持有股票状态的最大收益)为-prices[0]。由于第0天不能卖出股票,因此sell[0]为0 (表示第0天无收益)
  • 最后一天不持有的收益比持有的收益大(不持有说明已经卖掉了,持有说明还没卖掉,所以不持有的收益更大),所以返回sell的最后一个元素
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        vector<int>buy(n,0);
        vector<int>sell(n,0);
        buy[0]=-prices[0];
        for (int i = 1; i < n; ++i) {
            buy[i]=max(buy[i-1],sell[i-1]-prices[i]); // 持有股票状态
            sell[i]=max(sell[i-1],buy[i-1]+prices[i]); // 不持有股票状态
        }
        return sell.back();
    }
};

也可以用贪心方法解决:每天都买卖
Krahets的贪心思路

  • 连续上涨交易日,每天都买卖
    • p3-p1=(p2-p1)+(p3-p2) :第二天把p1卖掉,把p2买了;第三天把p2卖掉,把p3买了
  • 连续下跌交易日,则不买卖收益最大,即不会亏钱
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        profit = 0
        for i in range(1, len(prices)):
            tmp = prices[i] - prices[i - 1]
            if tmp > 0: profit += tmp # 当天的tmp若是正收益,则添加到profit中
        return profit

123. 买卖股票的最佳时机 III

  • 交易次数为2,是188题的特例,为构建通用的框架,我们解决188题

188. 买卖股票的最佳时机 IV

  • 交易次数为k

在122题的基础上,本题限制了交易次数,最多允许交易k次。变量有两个,天数和已交易的次数,因此dp数组为二维:

  • buy[i][j]:第i天持有股票状态时,一共已经交易了j次了以后的最大收益
    • 在持有(买)股票时如果用了一次交易次数(需要改变次数j):buy[i][j]=sell[i-1][j-1]-prices[i]
  • sell[i][j]:第i天不持有股票状态时,一共已经交易了j次了以后的最大收益
    • 在不持有(卖)股票时,则不需要改变次数j (因为在买的时候已经改变过了):sell[i][j]=buy[i-1][j]+prices[i]
  • 二维数组有n行(因为是n天),k+1列(如k=2,则可以选择0次、1次、2次交易,所以要k+1列才能存下状态)
class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int n=prices.size();
        if(n<=1)    return 0; // 只有0或1天时,无法完成完整的一次交易,返回0.
        vector<vector<int>>buy(n,vector<int>(k+1,0));
        vector<vector<int>>sell(n,vector<int>(k+1,0));
        for (int i = 0; i < k+1; ++i) {
            buy[0][i]=-prices[0]; // 第0天若处于持有状态,说明买入了(也可能是买了,又卖了,又买了...因此第一列的值都要初始化)当天的股票,此时收益为-prices[0]
        }
        for (int i = 1; i < n; ++i) {
            for (int j = 1; j < k+1; ++j) {
                buy[i][j]=max(buy[i-1][j],sell[i-1][j-1]-prices[i]);
                sell[i][j]=max(sell[i-1][j],buy[i-1][j]+prices[i]);
            }
        }
        return sell[n-1][k];
    }
};

309. 最佳买卖股票时机含冷冻期

添加冷冻期后,每天结束后有三种状态

  • 持有股票:buy[i]。这是因为:
    • 前一天持有股票,今天没操作:buy[i]=buy[i-1]
    • 前一天处于不持有股票且前一天结束后可以买,今天买了: buy[i]=sell[i-1]-prices[i]
    • 取这两种情况的最大值
  • 不持有股票,当天结束后可以买:sell[i]。这是因为:
    • 前一天不持有股票且前一天结束后可以买,今天没操作:sell[i]=sell[i-1]
    • 前一天不持有股票且前一天结束后不可以买,今天就不是冷冻期了,所以变成可以买的状态:sell[i]=cooldown[i-1]
    • 取这两种情况的最大值
  • 不持有股票,当天结束后不可以买:cooldown[i]。这是因为:
    • 前一天持有股票,今天卖了:cooldown[i]=buy[i-1]+prices[i]

结果就是不持有股票时的最大值

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        vector<int>buy(n,0);
        vector<int>sell(n,0);
        vector<int>cooldown(n,0);
        buy[0]=-prices[0];
        for (int i = 1; i < n; ++i) {
            buy[i]=max(buy[i-1],sell[i-1]-prices[i]); // 持有股票状态
            sell[i]=max(sell[i-1],cooldown[i-1]); // 不持有股票状态,当天结束后可以买
            cooldown[i]=buy[i-1]+prices[i];// 不持有股票状态,当天结束后不可以买
        }
        return max(sell.back(),cooldown.back());
    }
};

714. 买卖股票的最佳时机含手续费

  • 在122题的基础上,把买股票时的手续费扣除即可
class Solution {
public:
    int maxProfit(vector<int> &prices, int fee) {
        int n = prices.size();
        vector<int> buy(n, 0);
        vector<int> sell(n, 0);
        buy[0] = -prices[0] - fee;
        for (int i = 1; i < n; ++i) {
            buy[i] = max(buy[i - 1], sell[i - 1] - prices[i] - fee); // 持有股票状态
            sell[i] = max(sell[i - 1], buy[i - 1] + prices[i]); // 不持有股票状态
        }
        return sell.back();
    }
};

   转载规则


《第零章:一个方法团灭LEETCODE股票买卖问题》 M 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
第一章:手把手带你刷二叉树-第三期 第一章:手把手带你刷二叉树-第三期
labuladong手把手带你刷二叉树(第三期) Q:怎么知道以自己为根节点的二叉树长什么样? A:可以通过后序遍历(根的左子树+根的右子树+根)得到自己的样子。如果某个子节点处为空,则用#进行填空 Q:怎么知道和别人长得是否一样?
2021-09-23
下一篇 
第零章:单链表的六大解题套路,你都见过么? 第零章:单链表的六大解题套路,你都见过么?
labuladong单链表的六大解题套路,你都见过么? 21. 合并两个有序链表方法1:迭代 逐个比较节点,不断向后移动,最后把未比较过的节点拼接到尾巴上 添加一个dummy节点便于操作 /** * Definition for sin
2021-09-09
  目录