动态规划之我不是股神(力扣C++题解)

写题解仅为自我回顾

理论内容:

代码随想录代码随想录PDF,代码随想录百度网盘,代码随想录知识星球,代码随想录八股文PDF,代码随想录刷题路线,代码随想录知识星球八股文https://www.programmercarl.com/0121.%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.html


目录

1:买卖股票的最佳时机

 2:买卖股票的最佳时机II

3:买卖股票的最佳时机含手续费

4:买卖股票的最佳时机III(困难)

5:买卖股票的最佳时机IV(困难)

6:最佳买卖股票时机含冷冻期(最难)


1:买卖股票的最佳时机

 思路解析:

本题有三种方法,逐一介绍

1:暴力解法

遍历两次数组,两个for循环,用每次遍历让result=max(result,prices[j]-prices[i])来记录并更新

代码实现:

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

 

2:贪心算法

要使利润最大,那么就要保证我选择买的股票价格是最便宜的,我选择卖出的股票在那天是最贵的,并且买股票必须在卖股票之前;遍历一遍数组

用min取寻找最小的价格的股票:low=min(low,prices[i]);

用max取寻找最大的利润:result=max(result,prices[i]-low);

 代码实现:

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

 

3:动态规划

(1)设想:假设刚开始没有钱,手头的钱为0,那么dp根据列坐标可根据题意分为两类

···1:dp[i][0]持有股票时的所得的最多钱

持有股票相当于:我可能是今天买的,也可能是前几天买的,此时我持有股票

递推公式:dp[i][0]=max(dp[i-1][0],-prices[i])

解释:此时我持有股票,我啥都不干,我的钱没有损失,等于上一个的结果;

此时我想要买下这个股票,那么我的现金就要从0变成-prices[i]:当日股票的价格负数

···2:dp[i][1]不持有股票时所得的最多钱

不持有股票相当于:我可能是今天卖的,也可能是前几天卖的,此时我没有股票

递推公式dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i])

解释:此时我不持有股票,我啥都不敢,我的钱没有损失,等于上一个的结果;

此时我想卖掉这个股票,那么我的现金就要从上一个的现金结果里+这个股票的售价

(2)创建二维dp数组:vector<vector<int>>  dp(prices.size(),vector<int>(2))

因为要遍历这个prices数组,所以行的长度为prices.size()

因为要存储两种状态 [0]  [1]:所以列的长度为:2 

(3)初始化数组:

不难发现,这个dp数组的基石为:dp[0][0]和dp[0][1]

dp[0][0]含义:在第0天所持有股票的现金最大价值,既然是第0天,那么必然就是在一开始的时候就买下了股票,所以初始为:dp[0][0]=-prices[0];

dp[0][1]含义:在第0天所不持有股票现金的最大价值,意思就是我还没有股票,那么我一开始的钱又是0,所以初始化为:dp[0][1]=0

其他元素,为了不覆盖推导处理出来的元素初始化为0即可

(4)遍历顺序:

由递推公式我们可以知道,后一个是由前一个推导出来的,所以正序遍历即可

(5)返回结果

结合dp数组的含义:第i天所拥有的最多现金,那么肯定是卖了股票,才会使得现金增加,所以我们返回最后一天,不持有股票的dp:return dp[prices.size()-1][1]

代码实现:

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

另外:

这样做的效率并不高,但主要是逻辑清晰,在下面的难题是你会有更深的体会,下面的题目只有特殊的才特别强调,模板都是一样的


 2:买卖股票的最佳时机II

 贪心解法:

只要后面的一个比我大,那我就把股票出售,计算此时的差价,并将差价累计起来

代码实现:

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

动态规划:

与上题不同的地方在于,本题可以进行多次交易,那么只需要一点地方改变即:

上一题的持有股票的递推公式为:dp[i][0]=max(dp[i-1][0],-prices[i]);

那么如果我们一开始的钱不是0呢?就比如我已经做过了一次交易,我兜里有些钱,那么你就不能像看穷光蛋一样,看我了,即:

用上一次不持有股票的最大现金-这个股票的价格(意味着,我买入这个股票)

所以本题的持有股票的递推公式为:dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i])

代码实现:

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

3:买卖股票的最佳时机含手续费

 思路解析:

动态规划:

相当于上一题加上了手续费而已,手续费是在卖出去的时候需要减去的东西

那么就很简单了:稍微改变第二个递推公式即可

dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]-fee)

 代码实现:

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

4:买卖股票的最佳时机III(困难)

 思路解析:

动态规划:

与无限次买卖股票相比,乍一看最多只用搞两次,好像还比较简单,确实比较简单

只不过不能再像无限次一样只考虑两种状态了

总共有5种状态:

0:没有操作

1:第一次买,(第一次持有股票)->>初始化为-prices[0]

2:第一次卖,==上一次持有股票+股票的售价

3:第二次买,(第二次持有股票)->>初始化为-prices[0]

4:第二次卖,==上一次持有给股票+股票的手机

与上题的特殊不同在于,并不再是你推我我推你,而是:

0->1   1->2   2->3   3->4  

其实没有0情况也是可以的,只是为了看起来逻辑清晰而已

关于第二次买入的初始化:

第二次买入依赖于第一次卖出的状态,其实相当于第0天第一次买入了,第一次卖出了,然后在买入一次(第二次买入),那么现在手头上没有现金,只要买入,现金就做相应的减少

代码实现:

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

5:买卖股票的最佳时机IV(困难)

 思路解析:

动态规划:

还是一样的套路,但这次不是无限次,也不是2次,而是作为一个参数k次

但是通过上题我们已经发现了规律:总共有2*k+1个状态

0:啥都不干

1:第一次买入

2:第一次卖出

3:第二次买入

4:第二次卖出

5:第三次买入

6:第三次卖出

当次数为奇数次时:买入;当次数为偶数次时,卖出

我们总不能都像上题一样列出来吧,这样的是不可能完成的,重复性的工作:循环可以解决

在遍历的下一层加上:完美解决

for (int j = 0; j < 2 * k - 1; j += 2) {
    dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
    dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}

完整代码:

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {

        if (prices.size() == 0) return 0;
        vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));
        for (int j = 1; j < 2 * k; j += 2) {
            dp[0][j] = -prices[0];
        }
        for (int i = 1;i < prices.size(); i++) {
            for (int j = 0; j < 2 * k - 1; j += 2) {
                dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
                dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
            }
        }
        return dp[prices.size() - 1][2 * k];
    }
};

6:最佳买卖股票时机含冷冻期(最难)

 思路解析:推导过程

分析状态共有多少种:

1:持有股票阶段(可能是之前买的,也可能是今天买入股票)

2:卖出股票阶段                                                                                           

····1:两天前就卖出了股票

····2:今天卖出了股票

4:处于冻结期内

推导状态方程

操作1:仍保持持有股票阶段:dp[i][0]=dp[i-1][0]

操作2:今天买入股票

····1:前一天是冷冻期:dp[i-1][3]-prices[i]

````2:前一天是保持卖出股票状态:dp[i-1][1]-prices[i]

操作二取最大值:max(dp[i - 1][3], dp[i - 1][1]) - prices[i]

那么dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3], dp[i - 1][1]) - prices[i])

保持卖出股票阶段:

····1:前两天卖出股票,并且度过冰冻期

 dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);

````2:今天卖出股票:

dp[i][2]=dp[i-1][0]+prices[i]

冰冻期:卖出股票的下一天进入冰冻期

dp[i][3]=dp[i-1][2]

初始化问题:

将持有股票的dp[0][0]=-prices[0]

最后结果问题:

一般是从状态二和状态三,卖完股票那天,或不持有股票股票的状态出发

但是,会有部分样例无法通过,因为有可能,是在冰冻期内产生最大值:卖完股票的下一天

 代码实现:

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

重申:题解仅为自我回顾,理论内容来自

代码随想录代码随想录PDF,代码随想录百度网盘,代码随想录知识星球,代码随想录八股文PDF,代码随想录刷题路线,代码随想录知识星球八股文https://www.programmercarl.com/0309.%E6%9C%80%E4%BD%B3%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E6%97%B6%E6%9C%BA%E5%90%AB%E5%86%B7%E5%86%BB%E6%9C%9F.html#%E6%80%9D%E8%B7%AF

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值