这是一个系列,也是dp经典题,特别写篇博客。

leetcode 121 买卖股票的最佳时机 简单
只能买卖一次股票,那只需要从第一天开始一次遍历,不停地更新买的最低价和能赚到的最大利润即可。

参考代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int ans=0,low=999999;
        for(int i=0;i<prices.size();i++)
        {
            if(prices[i]<low)
                low=prices[i];
            else
                ans=max(ans,prices[i]-low);
        }
        return ans;
    }
};

leetcode122 买卖股票的最佳时机II 简单
可以多次买卖股票,但同时只能持有一支股票
只需要贪心即可,不停地扩大利润。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int last=prices[0],ans=0;
        for(int i=1;i<prices.size();i++)
        {
            if(prices[i]<last)
                last=prices[i];
            else
            {
                ans+=prices[i]-last;
                last=prices[i];
            }
        }
        return ans;
    }
};

当然,差分数组也是可以的,但理解有些困难,因为它并没有模拟买入卖出的过程,但其本质和上述代码是一样的。

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

当然,还可以用动态规划,虽然在这道题上有些多余,但可以用来为接下来的题做铺垫。

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

力扣123 买卖股票的最佳时机III 困难
一下子从简单难度跃升为困难。
最多可以完成两笔交易,但还是只能同时最多拥有一支股票。

有五种状态:未卖出且不持有股票的(状态0),买过一次但还没脱手的(状态1),买卖一次的(状态2),买卖一次后又买了但没脱手(状态3),两次买卖全部完成(状态4)。
从未卖出且不持有(永不入坑)
根据经验,我们可以得到状态转移方程,看上去完美无缺,不是么?
问题来了,两次买卖完成,至少是在第4天,即i=3才能完成,然而在循环中,第二次就会有值了。与现实不符!
错误代码样例:

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

力扣309 最佳买卖股票时机含冷冻期 中等

最多有一支股票,但卖出后必须停手一天(冷冻期)。
依然是动态规划。
思考一下状态:分别有持有股票,不持有股票处于冷冻期以及不持有股票不处于冷冻期。
有了经验,dp[i][j]表示i状态第j天时的最大值。
那么不妨设持有股票为状态0,不持有股票处于冷冻期为状态1,不持有股票不处于冷冻期为状态2。

dp[0][i]表示在第i天持有股票,那么它可以是第i-1天买来在第i天未售出,或是第i天刚买的。如果是第i天买的,要求第i-1天不持有股票不处于冷冻期。那么状态转移方程显而易见:dp[0][i]=max(dp[0][i-1],dp[2][i-1])
dp[1][i]表示在第i天不持有股票且处于冷冻期,那么它只可能是当天卖了股票,那么在第i-1天他一定持有股票,那么状态转移方程也很明显:dp[1][i]=dp[0][i-1]+prices[i]
dp[2][i]表示在第天不持有股票不处于冷冻期,它可能是刚从冷冻期解冻出来,也可能是之前就已经解冻但没有购买股票,那么状态方程也可以写出来了:dp[2][i]=max(dp[1][i-1],dp[0][i-1])

根据上述分析,在第0天,dp[0][0]应该是买入第0天的股票即-prices[i],dp[1][i]是不可能存在的状态,即0,dp[2][i]为0
而最后我们只需要求最后一天三种状态的最大值即可。(事实上第0种状态即持有股票对答案是无意义的,因为此时他手持股票,必然亏本。)

参考代码

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