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[i−1][0], 或者前一天结束的时候手里持有一支股票, 即
d
p
[
i
−
1
]
[
1
]
d p[i-1][1]
dp[i−1][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[i−1][0],dp[i−1][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[i−1][1], 或者前一天结束时还没有股票,即
d
p
[
i
−
1
]
[
0
]
d p[i-1][0]
dp[i−1][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[i−1][1],dp[i−1][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[n−1][0] 的收益必然是大于 d p [ n − 1 ] [ 1 ] d p[n-1][1] dp[n−1][1] 的,最后的答案即为 d p [ n − 1 ] [ 0 ] 。 d p[n-1][0]_{\\text {。 }} dp[n−1][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}] [buy−fee,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}] [buy−fee,buy] 内,它的价格没有低到我们放弃手上的股票去选择它,也没有高到我们可以通过卖出获得收益,因此我们不进行任何操作。
上面的贪心思想可以浓缩成一句话,即当我们卖出一支股票时,我们就立即获得了以相同价格并且免除手续费买入一支股票的权利。 在遍历完整个数组 prices \\textit{prices} prices 之后 ,我们就得到了最大的总收益。
class Solution:
def 以上是关于714. 买卖股票的最佳时机含手续费的主要内容,如果未能解决你的问题,请参考以下文章