DAY50:动态规划(十四)买卖股票最佳时机+买卖股票最佳时机Ⅱ

  • 本系列是DP经典系列,Ⅰ和Ⅱ要放在一起看,理解状态转移的定义
  • 这个系列DP的前提,都是把利润,转化成了每天手上的现金,让第i天手上的现金,是i天以来的最大值即可

121.买卖股票最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0

提示:

  • 1 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^4

思路1:暴力枚举

本题是买卖一次股票,也就是说整个过程只能买入一次和卖出一次

最直白的解决思路是暴力枚举,两层for循环,枚举每一个买股票的位置和卖股票的位置

思路2:贪心

本题目标是找出一段时间内的最大收益。

  • 在遍历所有价格时,找到当前看过的价格中最低的价格,这个最低的价格就是最理想的买入价格
  • 用当前价格减去这个当前所有价格中的最低价格,得到当前利润。和之前计算的最大利润进行比较,取两者中的较大值作为新的最大利润

贪心解法如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int payLow = INT_MAX;//存放买入价格
        int result = 0;//存放利润
        for(int i=0;i<prices.size();i++){
            payLow = min(payLow,prices[i]);//找遍历以来的最低价格
            result = (prices[i]-payLow)>result?prices[i]:payLow;//不断更新最大利润
        }
		return result;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

思路3:动态规划

动态规划这种做法核心在于,把交易中获取的最大利润转换为每一天手里的现金数第i天手里的现金数额最大,就是获取的利润最大

DP数组的含义

第i天我们有两个状态,一个是买股票,另一个是不买股票。

因此,我们需要一个二维数组来表示描述每一天的状态

  • dp[i][0]表示第i天,手上持有股票状态下的最大收益(也就是当前的现金数)
  • dp[i][1]表示第i天,手上不持有股票状态下的最大收益(当前现金数)

最后我们要求的最大收益,就是dp[nums.size()-1][1],因为本题到最后一定不持有股票

递推公式

dp[i][0]持有股票状态下最大收益,如果一直持有股票,那么手里的现金是没有变化的。

也就是说,如果当天没有买入股票,那么dp[i][0]=dp[i-1][0]

但是如果当天买入了股票,也是持有股票的状态,那么dp[i][0]=-prices[i],也就是支出当前价格。注意这里是负数,因为买的时候收益是负的。

  • 本题股票只买卖一次,因此dp[i][0]只会买入一次,只需要考虑-prices[i]。如果买卖多次,持有股票最大收益会有区别。
dp[i][0] = max(dp[i-1][0],-prices[i]);//两种状态选最大收益

dp[i][1]不持有股票状态的最大收益。

如果一直都没有持有股票,那么最大收益就是dp[i-1][1]

如果当天卖出了股票,刚好成为不持有股票的状态,最大收益就是dp[i-1][0]+prices[i]

dp[i][1] = max(dp[i-1][1],dp[i-1][0]+prices[i]);
注意点:(重要,一定要理解)
  • 如果是当天卖出,那么手里的最大现金数应该还要和之前的买入状态dp[i-1][0]相加!
  • 但是当天买入,因为只有一次买入并且之前都不可能有买入,所以手里的现金一定是-prices[i],因为之前没有手里现金数目的累积值变化
  • 如果是允许多次买入与卖出,那么再次买的时候,需要考虑**dp[i-1][1]-prices[i](也就是昨天手里没有股票)的情况,因为此时现金会有累积的利润值**,现金值有转移关系;但是本题,只允许一次买入,dp[i-1][1]也会有数值(因为包含了假定在之前卖出的情况),但是和现在的现金值没有转移关系!因此不能进行状态转移,dp[i][0]当天买入股票的情况就是-prices[i]。
  • 重点是看,dp[i]的现金数,和之前的哪些数额存在状态转移关系
完整版
  • debug:注意数组的下标范围,如果数组大小是days+1那么数组最大下标是days,而days=prices.size(),**dp[prices.size()]是无意义的!**正确的定义dp数组方式应该是大小直接定义成nums.size()
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //创建二维DP数组,大小是天数i,天数i对应(0,0)两个状态
       int days = prices.size();
       //如果数组大小是days+1那么最大下标是days,而days是price的size,是不存在price数组中的!
       vector<vector<int>>dp(days+1,vector<int>(2,0));
        //下标0持有股票的现金数,下标1不持有股票的现金数
       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[days-1][1];//最后一定不持有
    }
};

122.买卖股票最佳时机Ⅱ

给你一个整数数组 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

和买卖股票Ⅰ的联系与区别

因为整个过程中,第i天只有持有股票不持有股票两种状态,因此我们对于每一天i的情况,都用一个二维DP数组的0和1下标:dp[i][0]dp[i][1],分别表示第i天持有股票的最大现金数为dp[i][0],和第i天不持有股票的最大现金数为dp[i][1]

dp[i][0]对应第i天持有股票,持有股票分为两种情况:
  • 之前就持有股票:dp[i-1][0]
  • 今天刚刚买入,也是今天持有股票:-prices[i](Ⅰ),dp[i-1][1]-prices[i](Ⅱ)

买卖股票Ⅰ和Ⅱ两题的关键区别就在于买卖的规则。在买卖股票Ⅰ问题中,只能进行一次买入和卖出,而且一定是先买后卖不允许同一天内买卖。这就意味着,在买入股票的那一天,当前手里的现金(或者说是利润)只能是初始现金(也就是0)减去当天的股票价格不能叠加上前一天卖出股票的利润,因为前一天不可能卖股票!

而在Ⅱ中,因为不限制买入卖出的次数,可以随意买卖,因此**”刚刚买入导致持有“这种情况导致的最大现金变化**,就需要加上往次买入卖出得到的利润,也就是前一天现有的现金dp[i-1][1]和当天买入-prices[i]相加

dp[i-1][1]表示的就是第i-1天,不持有股票,手里的最大现金。而这个状态可以是由两种状态转移过来的:一是第 i-1 天没有进行任何操作,维持第 i-2 天的不持有状态,二是第 i-1 天卖出股票。因此,这个值并不能直接和买入股票的操作叠加,因为买入股票前一定是没有股票的状态,也就是说在买卖股票Ⅰ问题中,一定不会在买入前卖出。

dp[i][1]对应第i天不持有股票。不持有股票也分为两种情况:
  • 之前就不持有:dp[i-1][0]
  • 今天刚刚卖出,卖出之后成为不持有:dp[i-1][0]+prices[i]

如果是今天刚刚卖出,说明之前有买入,也就是说必须与之前的现金状态进行累加,才能得到”如果今天卖出“得到的最大现金数。此时,Ⅰ和Ⅱ”今天卖出导致的现金状态变化“递推逻辑都是dp[i-1][0]+prices[i]

完整版

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<vector<int>>dp(prices.size(),vector<int>(2,0));
        //0是持有股票
        dp[0][0] = -prices[0];
        //1是不持有股票
        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];//最后卖了一定是收益更大的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值