买卖股票的最佳时机问题总结

买卖股票问题是经典的动态规划问题(有时候贪心也能解)。

121. 买卖股票的最佳时机

  1. dp[0]表示第i天不持有股票的最佳收益,dp[1]表示第i天持有股票的最佳收益
  2. dp[0] = max(dp[0], dp[1] + prices[i]),dp[0]可能由前一天就不持有股票或者前一天持有股票又卖掉了得出

​ dp[1] = max(dp[1], -prices[i]),dp[1]可能由前一天持有股票或者今天才买入股票得出

  1. 初始化dp[0] = 0, dp[1] = -prices[0]。
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<int> dp(2, 0);
        dp[1] = -prices[0];
        for (int i = 1; i < n; i++) {
            int temp = dp[0];
            dp[0] = max(dp[0], dp[1] + prices[i]);
            dp[1] = max(dp[1], -prices[i]);
        }
        return dp[0] > dp[1] ? dp[0] : dp[1];
    }
};

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

这题和上面那道题差不多,只是现在你可以多次买入多次卖出了,dp数组定义不变,dp[1]的递推方程要变一点,之前只能买入一次,所以在买入前收益肯定是0,所以dp[1] = max(dp[1], -prices[i]),现在买入前收益不一定是0了,可能是有收益的,所以 dp[1] = max(dp[1], dp[0] - prices[i])。代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<int> dp(2, 0);
        int n = prices.size();
        dp[1] = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[0] = max(dp[0], dp[1] + prices[i]);
            dp[1] = max(dp[1], dp[0] - prices[i]);
        }
        return dp[0] > dp[1] ? dp[0] : dp[1];
    }
};

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

这道题属实有点难度,但是如果明白了前面两道题的本质还是可以尝试解决的。

这三道题的动态规划的本质是什么,用dp表示当前所处的状态的,例如第一题中dp[1]表示第i天持有股票,持有股票不代表第i天就买了股票,也可能是第i-1天处于持有的状态,在这道题中,共有四种状态

  1. 第一次买入
  2. 第一次卖出
  3. 第二次买入
  4. 第二次卖出

例如dp[2]不代表第i天干了什么事,可能啥也没干,只是前面一天也处于dp[2]的状态。明白了这一点,转移方程就可以像前面两道题那样自然而然地写出来了。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<int> dp(4, 0);
        dp[0] = -prices[0];
        dp[2] = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[0] = max(dp[0], -prices[i]);
            dp[1] = max(dp[1], dp[0] + prices[i]);
            dp[2] = max(dp[2], dp[1] - prices[i]);
            dp[3] = max(dp[3], dp[2] + prices[i]);
        }
        return dp[1] > dp[3] ? dp[1] : dp[3];
    }
};

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

这题都没什么好说的,就是上面的题的翻版,只需要把状态都正确地表示出来即可,我这里奇数是买入,偶数是卖出。

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if (prices.size() == 0)
            return 0;
        vector<int> dp(2 * k + 1, 0);
        for (int i = 1; i < 2 * k + 1; i += 2) {
            dp[i] = -prices[0];
        }
        int n = prices.size();
        for (int i = 1; i < n; i++) {
            for (int g = 1; g < 2 * k; g += 2) {
                dp[g] = max(dp[g], dp[g - 1] - prices[i]);
                dp[g + 1] = max(dp[g + 1], dp[g] + prices[i]);
            }
        }
        int ans = 0;
        for (int i = 2; i < 2 * k + 1; i += 2) {
            ans = dp[i] > ans ? dp[i] : ans;
        }
        return ans;
    }
};

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

同上面说的一样,买卖股票的问题主要是看每一天有几种状态,然后写出每种状态的转移方程。这里一共有三种状态

  1. 持有股票
  2. 不持有股票并且今天没卖出股票
  3. 不持有股票并且今天卖出了股票

对于第0种情况,可能是来着昨天的持有股票状态或昨天没卖出今天买入了。即dp[0] = max(dp[0], dp[1] - prices[i]);

对于第1种情况,今天没卖出又不持有股票,只能来自昨天的不持有了,即dp[1] = max(dp[1], dp[2]);

对于第2种情况,今天卖出了股票,所以是dp[2] = dp[0] + prices[i]。

所以代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<int> dp(3, 0);
        dp[0] = -prices[0];
        int n = prices.size();
        for (int i = 1; i < n; i++) {
            dp[0] = max(dp[0], dp[1] - prices[i]);
            dp[1] = max(dp[1], dp[2]);
            dp[2] = dp[0] + prices[i];
        }
        return dp[1] > dp[2] ? dp[1] : dp[2];
    }
};

这里说一下三个递推方程写的顺序为什么是这样,其实写的顺序是看递推方程的意义,dp[0]要用到昨天的dp[1],所以在dp[0]前半年修改dp[1], dp[1]要用到昨天的dp[1]和dp[2],所以在dp[1]前不能先修改dp[2]。

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

做到这里这题就很简单了,动规的时候加上手续费即可。

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        vector<int> dp(2, 0);
        dp[0] = -prices[0];
        int n = prices.size();
        for (int i = 1; i < n; i++) {
            dp[0] = max(dp[0], dp[1] - prices[i]);
            dp[1] = max(dp[1], dp[0] + prices[i] - fee);
        }
        return dp[1];
    }
};

其实这题也可以用贪心来做,我觉得贪心的做法比动规要难想多了,可以看看LeetCode的官方题解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值