122. 买卖股票的最佳时机 II

Posted 炫云云

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了122. 买卖股票的最佳时机 II相关的知识,希望对你有一定的参考价值。

122. 买卖股票的最佳时机 II

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入: prices = [7,1,5,3,6,4]
输出: 7

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

股票买卖策略:

  • 单独交易日: 设今天价格 p 1 p_{1} p1 、明天价格 p 2 p_{2} p2, 则今天买入、明天卖出可赚取金额 p 2 − p 1 p_{2}-p_{1} p2p1 (负值代表亏损)。
  • 连续上涨交易日:设此上张交易日股票价格分别为 p 1 , p 2 , … , p n p_{1}, p_{2}, \\ldots, p_{n} p1,p2,,pn​​, 则第一天买最后一天卖收益最大,即 p n − p 1 ; p_{n}-p_{1} ; pnp1;​​ 等价于每天都买卖, 即 p n − p 1 = ( p 2 − p 1 ) + ( p 3 − p 2 ) + … + p_{n}-p_{1}=\\left(p_{2}-p_{1}\\right)+\\left(p_{3}-p_{2}\\right)+\\ldots+ pnp1=(p2p1)+(p3p2)++ ( p n − p n − 1 ) \\left(p_{n}-p_{n-1}\\right)_{ } (pnpn1)​。
  • 连续下降交易日:则不买卖收益最大,即不会矢钱。

动态规划

根据 「力扣」第 121 题的思路,需要设置一个二维矩阵表示状态。

第 1 步:定义状态

状态 dp[i][j] 定义如下 :

dp[i][j] 表示到下标为 i 的这一天,持股状态为 j 时,我们手上拥有的最大现金数。

注意:限定持股状态为 j 是为了方便推导状态转移方程,这样的做法满足 无后效性

其中:

  • 第一维 i 表示下标为 i 的那一天( 具有前缀性质,即考虑了之前天数的交易 );
  • 第二维 j 表示下标为 i 的那一天是持有股票,还是持有现金。这里 0 表示持有现金(cash),1 表示持有股票(stock)。

第 2 步:状态转移方程

  • 状态从持有现金(cash)开始,到最后一天我们关心的状态依然是持有现金(cash);

  • 每一天状态可以转移,也可以不动。状态转移用下图表示:

i i i​天不持股: d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] ) dp[i][0] = max(dp[i-1][0], dp[i-1][1] +prices[i]) dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i])​,为第 i − 1 i-1 i1​天不持股或者 第 i − 1 i-1 i1​​天持股出售。

i i i天持股: d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] ) dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]) dp[i][1]=max(dp[i1][1],dp[i1][0]prices[i]),为第 i − 1 i-1 i1天持股或者 第 i − 1 i-1 i1天不持股买进。

第 3 步:确定初始值

  • 如果什么都不做,dp[0][0] = 0
  • 如果持有股票,当前拥有的现金数是当天股价的相反数,即 dp[0][1] = -prices[i]
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        Len = len(prices)
        # 特殊判断
        if Len<2:
            return 0 
        dp = [[0]* 2 for _ in range(Len)]
        #dp[i][0] 下标为 i 这天结束的时候,不持股,手上拥有的现金数
        #dp[i][1] 下标为 i 这天结束的时候,持股,手上拥有的现金数
        # 状态转移:0 → 1 → 0 → 1 → 0 → 1 → 0

        #初始化:不持股显然为 0,持股就需要减去第 1 天(下标为 0)的股价
        dp[0][0] = 0
        dp[0][1] = -prices[0]

        #从第 2 天开始遍历
        for i in range(1,Len):
            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[-1][0]

优化空间

由于当前行只参考上一行,每一行就 2 个值,因此可以考虑使用「滚动变量」(「滚动数组」技巧)。

 class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        Len = len(prices)
        # 特殊判断
        if Len<2:
            return 0 

        # cash:持有现金
        # hold:持有股票
        # 状态转移:cash → hold → cash → hold → cash → hold → cash

        cash = 0
        hold = -prices[0]

        preCash = cash
        preHold = hold

        #从第 2 天开始遍历
        for i in range(1,Len):
            cash  = max(preCash, preHold +prices[i])
            hold  = max(preHold ,preCash-prices[i])
        return cash

贪心算法

贪心算法的直觉:由于不限制交易次数,只要今天股价比昨天高,就交易。

下面对这个算法进行几点说明:

该算法仅可以用于计算,但 计算的过程并不是真正交易的过程,但可以用贪心算法计算题目要求的最大利润。下面说明等价性:以 [1, 2, 3, 4] 为例,这 4 天的股价依次上升,按照贪心算法,得到的最大利润是:

res =  (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])
    =  prices[3] - prices[0]

仔细观察上面的式子,按照贪心算法,在下标为 123 的这三天,我们做的操作应该是买进昨天的,卖出今天的,虽然这种操作题目并不允许,但是它等价于:在下标为 0 的那一天买入,在下标为 3 的那一天卖出。

  • 为什么叫「贪心算法」

求解最优化问题的算法通常需要经过一系列的步骤,在每个步骤都面临多种选择。对于许多最优化问题,使用动态规划算法来求最优解有些杀鸡用牛刀了,可以使用更简单、更高效的算法。贪心算法(greedy algorithm)就是这样的算法,它在每一步都做出当时看起来最佳的选择。也就是说,它总是做出局部最优的选择,寄希望这样的选择能导致全局最优解。

  • 这道题 「贪心」 的地方在于,对于 「今天的股价 - 昨天的股价」,得到的结果有 3 种可能:① 正数,② 0,③负数。贪心算法的决策是: 只加正数
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        Len = len(prices)
        # 特殊判断
        if Len<2:
            return 0 
        res = 0
        for i in range(1,Len):
            diff = prices[i] - prices[i - 1]
            if diff >0:
                res +=diff
        return res

等价写法:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        Len = len(prices)
        # 特殊判断
        if Len<2:
            return 0 
        res = 0
        for i in range(1,Len):
            res += max(prices[i] - prices[i - 1],0)
        return res

参考

Krahets - 力扣(LeetCode) (leetcode-cn.com)

以上是关于122. 买卖股票的最佳时机 II的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode第122题—买卖股票的最佳时机II—Python实现

122. 买卖股票的最佳时机 II

122.买卖股票的最佳时机II

122.买卖股票的最佳时机II

leetcode 122: 买卖股票的最佳时机 II

122. 买卖股票的最佳时机 II