714. 买卖股票的最佳时机含手续费

Posted 炫云云

tags:

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

714. 买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

**注意:**这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8

解释:能够达到的最大利润: 
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8

动态规划

定义状态 d p [ i ] [ 0 ] d p[i][0] dp[i][0] 表示第 i i i 天交易完后手里没有股票的最大利润, d p [ i ] [ 1 ] d p[i][1] dp[i][1] 表示第 i i i 天交易完后手里持 有一支股票的最大利润 ( i (i (i​ 从 0 开始)。

考虑 d p [ i ] [ 0 ] d p[i][0] dp[i][0]​ 的转移方程,如果这一天交易完后手里没有股票,那么可能的转移状态为前一天已经没有股票,即 d p [ i − 1 ] [ 0 ] d p[i-1][0] dp[i1][0]​, 或者前一天结束的时候手里持有一支股票, 即 d p [ i − 1 ] [ 1 ] d p[i-1][1] dp[i1][1]​, 这时候我们要 将其卖出,并获得 prices [ i ] [i] [i]​ 的收益,但需要支付 f e e f e e fee​​ 的手续费。因此为了收益最大化,我们列出如 下的转移方程:
d p [ i ] [ 0 ] = max ⁡ { d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] +  prices  [ i ] −  fee  } d p[i][0]=\\max \\{d p[i-1][0], d p[i-1][1]+\\text { prices }[i]-\\text { fee }\\} dp[i][0]=max{dp[i1][0],dp[i1][1]+ prices [i] fee }
再来按照同样的方式考虑 d p [ i ] [ 1 ] d p[i][1] dp[i][1] 按状态转移,那么可能的转移状态为前一天已经持有一支股票,即 d p [ i − 1 ] [ 1 ] d p[i-1][1] dp[i1][1], 或者前一天结束时还没有股票,即 d p [ i − 1 ] [ 0 ] d p[i-1][0] dp[i1][0], 这时候我们要将其买入,并减少 prices [ i ] [i] [i] 的收益。可以列出如下的转移方程:
d p [ i ] [ 1 ] = max ⁡ { d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − prices ⁡ [ i ] } d p[i][1]=\\max \\{d p[i-1][1], d p[i-1][0]-\\operatorname{prices}[i]\\} dp[i][1]=max{dp[i1][1],dp[i1][0]prices[i]}
对于初始状态,根据状态定义我们可以知道第 0 天交易结束的时候有 d p [ 0 ] [ 0 ] = 0 d p[0][0]=0 dp[0][0]=0 以及 d p [ 0 ] [ 1 ] = d p[0][1]= dp[0][1]= − p r i c e s [ 0 ] -prices[0] prices[0]

因此,我们只要从前往后依次计算状态即可。由于全部交易结束后,持有股票的收益一定低于不持 有股票的收益, 因此这时候 d p [ n − 1 ] [ 0 ] d p[n-1][0] dp[n1][0] 的收益必然是大于 d p [ n − 1 ] [ 1 ] d p[n-1][1] dp[n1][1] 的,最后的答案即为 d p [ n − 1 ] [ 0 ] 。  d p[n-1][0]_{\\text {。 }} dp[n1][0] 

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

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

        cash = 0
        hold = -prices[0]

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

方法一中,我们将手续费放在卖出时进行计算。如果我们换一个角度考虑,将手续费放在买入时进行计算,那么就可以得到一种基于贪心的方法。

这道题 「贪心」 的地方在于,对于 buy \\textit{buy} buy = prices [ 0 ] + fee \\textit{prices}[0]+\\textit{fee} prices[0]+fee p r i c e s [ i ] prices[i] prices[i] 比较,得到的结果有 3 种可能:

  • p r i c e s [ i ] + f e e < b u y prices[i] + fee < buy prices[i]+fee<buy
  • p r i c e s [ i ] > b u y prices[i] > buy prices[i]>buy
  • p r i c e s [ i ] prices[i] prices[i] 落在区间 [ buy − fee , buy ] [\\textit{buy}-\\textit{fee}, \\textit{buy}] [buyfee,buy]

buy \\textit{buy} buy 表示股票最低买入价格 。在初始时, buy \\textit{buy} buy​​​ = prices [ 0 ] + fee \\textit{prices}[0]+\\textit{fee} prices[0]+fee​​​ 。那么当我们遍历到第 i   ( i > 0 ) i~(i>0) i (i>0)​​​ 天时:

  • p r i c e s [ i ] + f e e < b u y prices[i] + fee < buy prices[i]+fee<buy :那么与其使用 buy \\textit{buy} buy 的价格购买股票,我们不如以 p r i c e s [ i ] + f e e prices[i] + fee prices[i]+fee 的价格购买股票,因此将 buy = p r i c e s [ i ] + f e e \\textit{buy}=prices[i] + fee buy=prices[i]+fee

  • p r i c e s [ i ] > b u y prices[i] > buy prices[i]>buy :直接卖出股票并且获得 p r i c e s [ i ] − b u y prices[i] -buy prices[i]buy 的收益。

    但实际上,卖出股票可能并不是全局最优的(例如下一天股票价格继续上升),因此我们可以提供一个反悔操作,看成当前手上拥有一支买入价格为 p r i c e s [ i ] prices[i] prices[i] 的股票 , buy = p r i c e s [ i ] \\textit{buy}=prices[i] buy=prices[i] 。这样一来,如果下一天股票价格继续上升,我们会获得 p r i c e s [ i + 1 ] − p r i c e s [ i ] prices[i+1] - prices[i] prices[i+1]prices[i]​ 的收益,恰好就等于在这一天不进行任何操作,而在下一天卖出股票的收益

  • 对于其余的情况, p r i c e s [ i ] prices[i] prices[i] 落在区间 [ buy − fee , buy ] [\\textit{buy}-\\textit{fee}, \\textit{buy}] [buyfee,buy] 内,它的价格没有低到我们放弃手上的股票去选择它,也没有高到我们可以通过卖出获得收益,因此我们不进行任何操作。

上面的贪心思想可以浓缩成一句话,即当我们卖出一支股票时,我们就立即获得了以相同价格并且免除手续费买入一支股票的权利。 在遍历完整个数组 prices \\textit{prices} prices 之后 ,我们就得到了最大的总收益。

class Solution:
    def 以上是关于714. 买卖股票的最佳时机含手续费的主要内容,如果未能解决你的问题,请参考以下文章

714. 买卖股票的最佳时机含手续费

714. 买卖股票的最佳时机含手续费

LeetCode——714. 买卖股票的最佳时机含手续费.

714. 买卖股票的最佳时机含手续费

Leetcode刷题Python714. 买卖股票的最佳时机含手续费

LeetCode 714. 买卖股票的最佳时机含手续费