算法力扣刷题记录 八十八【122.买卖股票的最佳时机 II】

前言

在本文之前,纠正了回溯章节中去重方式和排序之间的关系,可以去:记录 八十【491.递增子序列及去重方式——排序关系】中回顾。

贪心章节第5篇。动态规划第11篇。记录 八十八【122.买卖股票的最佳时机 II】


一、题目阅读

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3。
最大总利润为 4 + 3 = 7 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
最大总利润为 4 。

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0。

提示:

1 <= prices.length <= 3 * 10^4
0 <= prices[i] <= 10^4

二、尝试实现

2.1 分析题目,确定方法

  1. 题目要求最大利润。虽然可以同一天购买,同一天出售,但是这个利润是0。所以先不考虑同一天买同一天卖这种情况;

  2. 分析示例,确定如何获得最大值:启发——记录 七十九【376. 摆动序列】,所以才想到改成折线图看看
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  3. 所以:贪心算法可以解决。

  • 局部最优:每个单调递增坡的最低点买入,最高点卖出;一个递增坡获得最大利润
  • 全局最优:所有的递增坡获得整体的最大利润。

2.2 贪心思路实现

  1. 沿用记录 七十九【376. 摆动序列】的思路和细节进行处理;用pridiff和curdiff表示递增还是递减。
  2. 定义 int pay = 0;代表股票买入的价格。int sale = 0;表示股票卖出的价格。
  3. 以示例1,较为规律的序列
  • if(pridiff < 0 && curdiff > 0) ,代表一个单调递增坡的起点,应该买入。类似节点1,节点3;

  • if(pridiff > 0 && curdiff < 0),代表一个单调递增坡的终点,应该卖出。类似节点5,节点6;

  • for循环:从0开始到prices.size()-1结束;

  • pridiff初始为0;curdiff = prices[i+1]-prices[i];。

  • 同时result代表全局的最大利润,应该求和每一个单调递增坡的最大利润。result += sale-pay;

  • 所以这种情况下:代码如下——

    class Solution {
    public:
        int maxProfit(vector<int>& prices) {
            int result = 0;
            int pridiff = 0;
            int curdiff = 0;
            int pay = 0;//购入的价格
            int sale = 0;//出售的价格
            for(int i = 0;i < prices.size()-1;i++){
                curdiff = prices[i+1]-prices[i];
                if(pridiff < 0 && curdiff > 0) {
                    pay = prices[i];
                }
                if(pridiff > 0 && curdiff < 0){
                    sale = prices[i];
                    result += sale-pay; //求和每次买卖的价格
                }
                pridiff = curdiff;
            }
            
            return result;
        }
    };
    
  1. 补充情况一:起点和终点如何处理
  • 记录 七十九【376. 摆动序列】起点构造了一个平坡,pridiff = 0。在这里起点应该构造一个下降的坡。理由如下:
    在这里插入图片描述
  • 终点处理:扩展for循环到prices.size()-1停止。终止的curdiff = -1,表示终点是一个单调坡的最高点。理由如下:
    在这里插入图片描述
  • 所以:起点和重点的处理,使得始终单调递增(示例2)和始终单调递减(示例3)得以解决,所以代码更改:
    • pridiff = -1;初始化。//构造起点的下降坡
    • for循环范围到i < prices.size();
    • if(i == prices.size()-1) curdiff = -1;//构造终点的下降坡
    class Solution {
    public:
        int maxProfit(vector<int>& prices) {
            int result = 0;
            int pridiff = -1;//构造起点的下降坡
            int curdiff = 0;
            int pay = 0;//购入的价格
            int sale = 0;//出售的价格
            for(int i = 0;i < prices.size();i++){
                if(i == prices.size()-1) {
                    curdiff = -1;//构造终点的下降坡
                }else{
                    curdiff = prices[i+1]-prices[i];
                }
                if(pridiff < 0 && curdiff > 0) {
                    pay = prices[i];
                }
                if(pridiff > 0 && curdiff < 0){
                    sale = prices[i];
                    result += sale-pay; //求和每次买卖的价格
                }
                pridiff = curdiff;
            }
            
            return result;
        }
    };
    
  1. 补充情况二:如果遇到平坡怎么办?以平坡的最后一个元素作为买/卖的节点。
  • 所以两个pridiff和curdiff的判断条件改成:

  • if(pridiff <= 0 && curdiff > 0) 和 if(pridiff >= 0 && curdiff < 0)
    在这里插入图片描述
    所以代码改变:

    class Solution {
    public:
        int maxProfit(vector<int>& prices) {
            int result = 0;
            int pridiff = -1;//构造起点的下降坡
            int curdiff = 0;
            int pay = 0;//购入的价格
            int sale = 0;//出售的价格
            for(int i = 0;i < prices.size();i++){
                if(i == prices.size()-1) {
                    curdiff = -1;//构造终点的下降坡
                }else{
                    curdiff = prices[i+1]-prices[i];
                }
                if(pridiff <= 0 && curdiff > 0) {
                    pay = prices[i];
                }
                if(pridiff >= 0 && curdiff < 0){
                    sale = prices[i];
                    result += sale-pay; //求和每次买卖的价格
                }
                pridiff = curdiff;
            }
            
            return result;
        }
    };
    
  1. 补充情况三:单调坡中有平坡怎么办?所以pridiff = curdiff;不应该每个节点都更新。
  • 更新pay时,更新pridiff;更新sale时,更新pridiff。

  • 所以pridiff = curdiff;放到两个if条件里面。
    在这里插入图片描述
    代码更新:

    class Solution {
    public:
        int maxProfit(vector<int>& prices) {
            int result = 0;
            int pridiff = -1;
            int curdiff = 0;
            int pay = 0;//购入的价格
            int sale = 0;//出售的价格
            for(int i = 0;i < prices.size();i++){
                if(i == prices.size()-1) {
                    curdiff = -1;
                }else{
                    curdiff = prices[i+1]-prices[i];
                }
                if(pridiff <= 0 && curdiff > 0) {
                    pay = prices[i];
                    pridiff = curdiff;
                }
                if(pridiff >= 0 && curdiff < 0){
                    sale = prices[i];
                    result += sale-pay;
                    pridiff = curdiff;
                }
                
            }
            
            return result;
        }
    };
    

2.3 代码实现【贪心算法】

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int result = 0;
        int pridiff = -1;
        int curdiff = 0;
        int pay = 0;//购入的价格
        int sale = 0;//出售的价格
        for(int i = 0;i < prices.size();i++){
            if(i == prices.size()-1) {
                curdiff = -1;
            }else{
                curdiff = prices[i+1]-prices[i];
            }
            if(pridiff <= 0 && curdiff > 0) {
                pay = prices[i];
                pridiff = curdiff;
            }
            if(pridiff >= 0 && curdiff < 0){
                sale = prices[i];
                result += sale-pay;
                pridiff = curdiff;
            }
            
        }
        
        return result;
    }
};

三、参考学习

【122.买卖股票的最佳时机 II】 参考学习链接

3.1 【122.买卖股票的最佳时机 II】贪心思路

  1. 回到题目中:“在每一天,你可以决定是否购买 和/或出售股票。”——含义是:当天可以只买入或者只卖出或者既买入又卖出。很明显:同一天买入同一天卖出没有利润,因此至少需要两天完成一个交易。所以利润可以进行拆分
  2. 如何进行利润拆分?如下图:
    在这里插入图片描述
  3. 总结:
  • 个人思路:从记录 七十九【376. 摆动序列】中获得区间单调坡的概念,所以使用区间的利润可以找到一个最低点买入,最高点卖出。但是情况思考比较多,处理细节也繁琐。
  • 参考思路:发现利润可以拆分,所以只搜集正数即可。

3.2 代码实现【贪心:参考思路】

边遍历边搜集利润中的正数。

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

3.3 动态规划思路

3.3.1思考

  1. 买卖股票系列是动态规划章节内容,但是贪心算法可以帮助一些题目巧妙解决,并不代表贪心可以解决所有买卖股票系列的题目。
  2. 那么尝试动态规划实现:
  3. 定义dp数组和下标:dp[i]代表在第i天卖出股票,所能获得的最大利润是dp[i]
  4. 递推公式:内层循环 j 遍历第i天之前的每一天,dp[i] = max(dp[i] , dp[j] + prices[i] - prices[j]);
    在这里插入图片描述
  5. 初始化:dp[0] = 0;在第0天卖出只能第0天买入,所以利润是0.
  6. 遍历顺序:从递推公式知道外层for循环从前往后。
  7. 返回值是dp数组中什么?所以dp数组的含义设定不正确。那么该如何设定dp数组

3.3.2 正确思路

  1. dp数组含义:
  • dp[i][0]:在第i天持有股票得到的现金dp[i][0];
  • dp[i][1]:在第i天不持有股票得到的现金dp[i][1];
  • 因为最多持股一个,所以当天要么有股票(1个),要么没有股票。
  • 所得现金 = 利润是一个累积值。返回值:最后一天不持有股票时所得现金,即dp[最后一个][1]
  1. 递推公式:
  • 求dp[i][0],在第i天持有股票,得到多少钱?
    • 第i天持有股票,要么第i-1天持有股票,今天继续持有不卖出,钱不增加不减少。所得现金:dp[i-1][0]。
    • 第i天持有股票,,要么第i-1天不持有股票,今天重新买入。钱在dp[i-1][1]的基础上减去买入的prices[i]。所得现金:dp[i-1][1] - prices[i]。
    • 取两者的最大值。dp[i][0] = max(dp[i-1][0],dp[i-1][1] - prices[i])
  • 求dp[i][1],在第i天不持有股票,得到多少钱?
    • 第i天不持有股票,要么第i-1天持有股票,今天以prices[i]卖出。钱在dp[i-1][0]基础上多了prices[i]。所得现金:dp[i-1][0] + prices[i];
    • 第i天不持有股票,要么第i-1天不持有股票,今天也不买入。钱依然是第i-1天不持有股票所得的钱。所以现金:dp[i-1][1]。
    • 取两者的最大值。dp[i][1] = max(dp[i-1][0] + prices[i],dp[i-1][1])
  1. 初始化:
  • dp[0][0] = -prices[0]。先买入,花掉了-prices[0]。
  • dp[0][1] = 0;相当于不买入。钱没花没赚。
  1. 遍历顺序:从递推公式看,从前往后。

3.4 代码实现【动态规划】

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        //定义dp数组
        vector<vector<int>> dp(len,vector<int>(2,0));
        //初始化
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        //遍历,递推公式
        for(int i = 1;i <len;i++){
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]- prices[i]);
            dp[i][1] = max(dp[i-1][0] + prices[i],dp[i-1][1]);
        }
        return dp[len-1][1];
    }
};

总结

本文:

  • 27
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值