买卖股票的最jia时机

(标题本来是 “最佳”,违反了标题规定哈哈哈,被当成标题党了。)

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

思路:

  股票买卖问题的一大类非常经典的题目。所有的股票买卖问题都可以用一个模板去解决。我们需要一个三维的动态规划数组来实现这个模板(此模板参考《labuladong的算法小抄》)。

模板(理解并记忆!):

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

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

1.首先清楚dp三维数组的定义:

其中第一位表示第几天,第二位表示交易次数,第三位表示是否持有股票

dp[i][k][j]表示第i天,总交易次数为k时,且持有情况为j时的最大利润。

  比如:

  dp[i][k][0]表示第i天,总交易次数为k,未持有股票(0)时的利润

  dp[i-1][k][1]表示第i天,总交易次数k,持有股票(1)时的利润

2.其次清楚状态转移:

对于第一句:dp[i][k][0]=max(dp[i-1][k][0],dp[i-1][k][1]+prices[i])

  意思是如果我们第i天没有持有股票(0),则要么我们前一天(i-1)也不持有,要么就在i-1天时持有但是卖出了。前者对应着dp[i-1][k][0],后者对应dp[i-1][k][1]+prices[i](卖出,当天股票价格多少就加多少钱)。其中哪个利润较大我们取哪个,用max。

对于第二句:dp[i][k][1]=max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i])

  意思是如果我们第i天持有了股票(1),则要么我们前一天(i-1)也持有着保持到选择,要么就是在i-1天时没有持有但买入了。前者对应dp[i-1][k][1],后者对应dp[i-1][k-1][0]-prices[i](买入,当天股票价格多少钱,就减多少钱)。其中哪个利润较大我们取哪个,用max。

  上面还需要注意的是我们k的变化,k代表着交易次数。在第一句中,我们有卖出的操作,但k其实并没有变动;在第二句中,买入股票时我们将交易次数k-1了。因为买入和卖出,才只凑成一套交易,因此只能选择在其中一个地方减少我们的交易次数。习惯上来看,就什么时候买了新股票,我们什么时候把交易次数减1即可。

  清楚了以上的动态规划数组的定义和转移方式,基本上任何股票买卖的问题都可以通过此方式解决了。而对于一些简单的股票买卖问题变式,我们其实也并不需要这么多参数。

  比如在这一题当中,因为我们交易次数只有一次,所以实际上模板就等同于:

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

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

         因为总交易次数k为0时,利润必然必然为0,所以可得:

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

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

  可以发现第二维全都是1,并无状态转移意义,因此第2维可以直接删除。因此,状态转移方程变最终为:

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

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

  最后,我们还要注意这个二维数组(删除了1维,不再是三维了)的初始化以及base case的定义。我们初始化所有值为空,特别声明dp[0][1]为负无穷,因为第0天显然不可能持有。

  需要注意,我们这里第1天就是dp[1],并不是从dp[0],这样方便理解。另外第一天的股票价格因为我们就是用的prices数组,得通过prices[0]来得到,因此在下面代码中状态转移方程prices的下标会有个-1,和模板中prices[i]表示第i天的价格稍有不同,这里知道就好。

代码:

class Solution(object):

    def maxProfit(self, prices):

    # dp[i][k][0]=max(dp[i-1][k][0],dp[i-1][k][1]+prices[i])#老模板,放在最上面参考

    # dp[i][k][1]=max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i])

        lenth = len(prices)#获取一共有几天(有几个价格就有几天)

        dp=[[0]*2 for _ in range(lenth+1)]#初始化二维数组,长为lenth+1,宽为2

        dp[0][1]=-float('inf')#定义base case 第0天却持有的情况不可能,初始化为负无穷

        for i in range(1,lenth+1):#从第一天开始向上生成

            dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i-1])#使用我们简化的模板

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

        return dp[lenth][0]#返回的就是最后一天,且未持有时的利润

        #毕竟最后肯定要卖出嘛,所以肯定是未持有

小结:

  我建议做股票买卖问题的时候,无论如何都先把我们最万能的终极公式写出来:

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

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

  然后我们根据题意去看看是否需要简化我们的状态转移方程,比如这一题我们知道交易次数只有1次,所以我们就把第二个参数去掉。

最后补两个特别注意点:

  一、股票买卖问题base case的定义也十分重要。根据我们的定义方式,我们知道1、第0天不可能持有,后面遇到其他题型时,我们还知道2、交易次数为0时不可能持有。对于不可能的情况我们定义成负无穷-float(‘inf’),这样此类情况在与其他合法情况做max时就不可能被选出了。这一题只用到了第一条,第二条以后其他变式会用到。

  二、还要特别注意:对于最终结果的选取,我们是取dp[lenth][k][0],而不是dp[lenth][0][0]因为第二维交易次数k并不是“当前剩余次数”,而是总次数。所以实际上在这一题,如果我们不删掉第二维,则结果取得应该是dp[lenth][1][0] (总交易次数为1)。这一要点也在以后其他变式题中会用到。

  所有的股票问题统一做一遍,就会发现其实都差不多。这一题我使用的方式是二维动态规划这样比较便于理解的方式,为了更小的空间复杂,我们可以压缩至一维——但那是在理解二维的基础上去修改代码。假如一开始就要去看最简单的写法,则几乎不可能理解其真正的意义,而且那种写法也不好记忆,只能是看别人写出来炫技罢了。

posted @   JunanP  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示