代码随想录训练营打卡第35天:动态规划解决买卖股票问题

1.121买卖股票的最佳时机

1.问题解析

买卖一次获得的最大利润。

2.问题转换

price[i]-price[j]的最大值。

3.解题思路

  1. 每一天都有两种状态:持有和不持有两种状态。
  2. 对于持有状态分为两种情况:1.在今天之前已经买入。2.今天买入,前一天是不持有状态
  3. 对于不持有状态分为两种情况:1.再今天之前已经卖出。2.今天卖出,前一天是持有状态
  4. 最后取得的最大值一定是不持有状态。

4.为什么要使用动态规划?

(本题也可以使用贪心算法来实现,但是考虑到与后面题目的连续性,所以这里只考虑使用动态规划)因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。

5.动态规划的具体实现

  1. dp数组的下标及其含义:dp[i][0]:代表的是第i天持有。dp[i][1]代表的是第i天不持有
  2. 递推公式:dp[i][0] = max(dp[i - 1][0], -prices[i]);//表示持有状态下获得的最大收益。dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);//表示不持有状态下获得的最大收益
  3. 初始化:第一天如果持有的话,那么代表的就是今天买入,收益为-price[0]。如果不持有的话,代表今天不进行任何操作即收益还是为0.
  4. 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //第一种方法:使用差值的方法进行计算即可
        /*
        int n = prices.size();
        int count = 0;
        int maxs = 0;
        for(int i = 0;i<n-1;i++){
            count += prices[i+1] - prices[i];
            if(count>maxs) maxs = count;
            if(count<0) count = 0;
        }
        return maxs;*/
        int len = prices.size();
        if (len == 0) return 0;
        vector<vector<int>> dp(len, vector<int>(2));
        dp[0][0] -= prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < len; i++) {
            dp[i][0] = max(dp[i - 1][0], 0-prices[i]);
            dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
        }
        return dp[len - 1][1];
    }
};

2.122买卖股票的最佳时机2

1.问题解析

买卖多次获得的最大利润。

2.问题转换

price[i]-price[j]为正值的累加和。

3.解题思路

  1. 每一天都有两种状态:持有和不持有两种状态。
  2. 对于持有状态分为两种情况:1.在今天之前已经买入。2.今天买入,前一天是不持有状态
  3. 对于不持有状态分为两种情况:1.再今天之前已经卖出。2.今天卖出,前一天是持有状态
  4. 最后取得的最大值一定是不持有状态。

4.为什么要使用动态规划?

(本题也可以使用贪心算法来实现,但是考虑到与后面题目的连续性,所以这里只考虑使用动态规划)因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。

5.动态规划的具体实现

  1. dp数组的下标及其含义:dp[i][0]:代表的是第i天持有。dp[i][1]代表的是第i天不持有
  2. 递推公式:dp[i][0] = max(dp[i-1][0],dp[i-1][1]-prices[i]);//表示持有状态下获得的最大收益(在这里由于可以多次买卖,所以如果是今天买入的话,前面的收益不再是0了,而是dp[i-1][1])。dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);//表示不持有状态下获得的最大收益。
  3. 初始化:第一天如果持有的话,那么代表的就是今天买入,收益为-price[0]。如果不持有的话,代表今天不进行任何操作或者买入又卖出,即收益还是为0.
  4. 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //第一种方法:贪心算法
        /*
        int val = 0;
        int count = 0;
        int n = prices.size();
        for(int i = 1;i<n;i++){
            count = prices[i]-prices[i-1];
            if(count>0){
                val += count;
            }
        }
        return val;*/
        int n = prices.size();
        vector<vector<int>> dp(n,vector<int>(2,0));
        //dp[i][0]:代表第i天持有的最大利润
        //dp[i][1]:代表第i天未持有的最大利润
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i = 1;i<n;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[n-1][1];
    }
};

3.123买卖股票的最佳时机

1.问题解析

买卖两次获得的最大利润。

2.问题转换

price[i]-price[j]的两个最大值。

3.解题思路

  1. 每一天都有五种状态:第一次持有、第二次持有、第一次不持有、第二次不持有和第三次不持有共五种状态。
  2. 对于第一次持有状态分为两种情况:1.在今天之前已经第一次买入了,今天不进行任何操作。2.今天第一次买入,前一天是第一次不持有状态。
  3. 对于第二次持有状态分为两种情况:1.在今天之前已经第二次买入了,今天不进行任何操作。2.今天第二次买入,前一天是第二次不持有状态。
  4. 对于第一次不持有状态:就是不进行任何操作。从来没有进行买入和卖出操作。
  5. 对于第二次不持有状态分为两种情况:1.在今天之前已经第一次的卖出了,今天不进行任何操作。2.今天是第一次卖出,前一天是第一次持有的状态。
  6. 对于第三次不持有状态分为两种情况:1.在今天之前已经第二次的卖出了,今天不进行任何操作。2.今天是第二次卖出,前一天是第二次持有的状态。
  7. 最后取得的最大值一定是第三次不持有状态。

4.为什么要使用动态规划?

因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。

5.动态规划的具体实现

  1. dp数组的下标及其含义:dp[i][0]:代表的是第i天第一次不持有。dp[i][1]代表的是第i天第一次持有。dp[i][2]:代表的是第i天第二次不持有。dp[i][3]代表的是第i天第二次持有。dp[i][4]:代表的是第i天第三次不持有。
  2. 递推公式: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]);
  3. 初始化:第一天第一次持有或者第二次,收益都是-price[0](因为第一天可以进行多次买入和卖出的操作,收益不变dp[0][3] = -prices[0]+prices[0]-prices[0]=-prices[0];)。如果不持有的话,代表今天不进行任何操作或者买入又卖出,即收益还是为0.
  4. 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //还有一种方法:就是使用一个vector存储正值,初始化的时候给一个0避免出现一种都是累加的求得的结果不够的情况
        /*
        int n = prices.size();
        vector<int> result;
        result.push_back(0);
        int count = 0;
        for(int i = 1;i<n;i++){
            int diff = prices[i]-prices[i-1];
            if(diff>=0){
                count += diff; 
            }else{
                result.push_back(count);
                count = 0;
            }
        }
        result.push_back(count);
        sort(result.begin(),result.end());
        int m = result.size();
        int ret = result[m-1]+result[m-2];
        return ret;
        */
        int n = prices.size();
        //dp[i][0]:无操作
        //dp[i][1]:第一次持有
        //dp[i][2]:第一次不持有
        //dp[i][3]:第二次持有
        //dp[i][4]:第二次不持有
        vector<vector<int>> dp(n,vector<int>(5,0));
        dp[0][1] = -prices[0];
        dp[0][3] = -prices[0];
        for(int i = 1;i < n;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[n-1][4];
    }
};

4.188买卖股票的最佳时机四

1.问题解析

买卖k次获得的最大利润。和上一题很类似,只是一个是具体的只有两次而这个是k次。

2.问题转换

price[i]-price[j]的k个最大值。

3.解题思路

  1. 每一天都有2k+1种状态:第一次持有...第k次持有、第一次不持有、第二次不持有...第k+1次不持有共五种状态。
  2. 对于第一次持有状态分为两种情况:1.在今天之前已经第一次买入了,今天不进行任何操作。2.今天第一次买入,前一天是第一次不持有状态。
  3. 对于第k次持有状态分为两种情况:1.在今天之前已经第k次买入了,今天不进行任何操作。2.今天第k次买入,前一天是第k次不持有状态。
  4. 对于第一次不持有状态:就是不进行任何操作。从来没有进行买入和卖出操作。
  5. 对于第二次不持有状态分为两种情况:1.在今天之前已经第一次的卖出了,今天不进行任何操作。2.今天是第一次卖出,前一天是第一次持有的状态。
  6. 对于第k+1次不持有状态分为两种情况:1.在今天之前已经第k次的卖出了,今天不进行任何操作。2.今天是第k次卖出,前一天是第k次持有的状态。
  7. 最后取得的最大值一定是第k+1次不持有状态。

4.为什么要使用动态规划?

因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。

5.动态规划的具体实现

  1. dp数组的下标及其含义:dp[i][0]:代表的是第i天第一次不持有。dp[i][1]代表的是第i天第一次持有。dp[i][2]:代表的是第i天第二次不持有。dp[i][3]代表的是第i天第二次持有。dp[i][4]:代表的是第i天第三次不持有。
  2. 递推公式: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]);
                }
  3. 初始化:第一天第一次持有或者第k次,收益都是-price[0](因为第一天可以进行多次买入和卖出的操作,收益不变dp[0][k] = (k-1)*(-prices[0]+prices[0])-prices[0]=-prices[0];)。如果不持有的话,代表今天不进行任何操作或者买入又卖出,即收益还是为0.
  4. 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
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];
    }
};

5.309买卖股票的最佳时机含冷冻期

1.问题解析

有冷冻期情况下的price[i]-price[j]的累加的最大值

2.问题转换

有约束条件下的price[i]-price[j]的累加的最大值

3.解题思路

  1. 每一天都有四种状态:保持买入(可以买入),保持卖出(已经过了冷冻期),今天卖出,冷冻期共四种状态。
  2. 对于保持买入分为三种情况:前一天是保持卖出的状态今天买入,前一天是冷冻期今天买入和前一天已经是买入状态今天不进行任何操作。
  3. 对于保持买入分为两种情况:前一天是保持卖出的状态今天不进行任何操作和前一天是冷冻期今天还是不进行任何操作。
  4. 对于今天卖出状态:前一天必然是保持买入状态,今天卖出。
  5. 对于冷冻期状态:前一天必然是卖出状态。
  6. 收益最大的时候一定是卖出的状态(今天卖出、保持卖出、冷冻期)。

4.为什么要使用动态规划?

因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。

5.动态规划的具体实现

  1. dp数组的下标及其含义:dp[i][0]:代表的是第i天保持买入状态。dp[i][1]代表的是第i天保持卖出的状态。dp[i][2]:代表的是第i天卖出。dp[i][3]代表的是第i天为冷冻期。
  2. 递推公式:dp[i][0] = max(dp[i-1][0],max(dp[i-1][3]-prices[i],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];//由来看前面的解题思路
  3. 初始化:第一天保持买入,收益都是-price[0]。默认其他情况下都是0(不持有状态,没有收益)。
  4. 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        //四种状态
        //状态0:保持买入的状态,三种情况,第一种是前一天就是买入状态,第二个是前一天是冷冻期,那么今天买入,第三个是保持卖出状态
        //状态1:保持卖出的状态,两种情况,第一种是前一时刻为冷冻期,第二个是前一时刻就是保持卖出状态。
        //状态2:今天卖出的状态,那么前一天是保持买入状态
        //状态3:冷冻期,那么前一天就是卖出状态。
        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]-prices[i],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][1],max(dp[n-1][2],dp[n-1][3]));//最后利润最大的时候一定是卖出的状态,但是卖出后的状态有三种
    }
};

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值