题目链接
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/
题目描述
给定价格数组prices,prices[i]表示一支给定股票第i天的价格。限定只能交易两次(卖掉手里的股票后才能再次购买),设计算法来计算能够获得的最大利润。
示例
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
i从0开始计数,在第3天(价格为0)买入,第5天(价格为3)卖出,得利润3。然后在第6天买入(价格为1),在第7天(价格为4)卖出,得利润3。因此总利润为6。
解题思路
此题为【leetcode-Python】-Dynamic Programming -121. Best Time to Buy and Sell Stock和【leetcode-Python】-Dynamic Programming -122. Best Time to Buy and Sell Stock II的拓展,限定最多交易次数为2。
参考k为任意值的问题【leetcode-Python】-Dynamic Programming -188. Best Time to Buy and Sell Stock IV,将k设定为2即可。
Python实现
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
if n < 1:
return 0
dp = [[0 for _ in range(3)] for _ in range(2)]
for i in range(3):
dp[1][i] = -prices[0]
dp[1][0] = -float('inf')
for i in range(1,n):
for k in range(2,0,-1): #这里k的范围如果是[2,-1)会出错。k=0是边界条件,不在这里更新。
dp[0][k] = max(dp[0][k],dp[1][k] + prices[i])
dp[1][k] = max(dp[1][k],dp[0][k-1] - prices[i])
return dp[0][2]
时间复杂度与空间复杂度
时间复杂度为O(N),空间复杂度为O(1)。
解题思路二
由于k比较小,因此可以仅用几个状态变量来保存、更新状态。每天可能的操作包括:无操作、买入、卖出三种。在最多交易次数为2的情况下,每天操作结束后,其状态包括以下五种:
“无任何操作”、“买入了一次”、“买入一次,卖出一次”、“买入两次,卖出一次”、“买入两次,卖出两次”
“无任何操作“的利润为0,无需更新。定义buy1(i)、sell1(i)、buy2(i)、sell2(i)分别表示第i天处于“买入了一次”、“买入一次,卖出一次”、“买入两次,卖出一次”、“买入两次,卖出两次”状态时的最大利润。
第i天处于“买入了一次”状态时,可能的情况包括:
(1)第i-1天处于“无任何操作”状态,第i天买入。对应利润为-pirces[i]
(2)第i-1天处于“买入了一次”状态,第i天无操作。对应利润为buy1(i-1)。
因此有buy1(i) = max(-pirces[i],buy1(i-1))。
第i天处于“买入一次,卖出一次”状态时,可能的情况包括:
(1)第i-1天处于“买入了一次”状态,第i天卖出。对应利润为buy1(i-1)+pirces[i]
(2)第i-1天处于“买入一次,卖出一次”状态,第i天无操作。对应利润为sell1(i-1)。
因此有sell1(i) = max(buy1(i-1)+pirces[i],sell1(i-1))。
第i天处于“买入两次,卖出一次”状态时,可能的情况包括:
(1)第i-1天处于“买入一次、卖出一次”状态,第i天买入。对应利润为sell1(i-1)-pirces[i]
(2)第i-1天处于“买入两次,卖出一次”状态,第i天无操作。对应利润为buy2(i-1)。
因此有buy2(i) = max(sell1(i-1)-pirces[i],buy2(i-1))。
第i天处于“买入两次,卖出两次”状态时,可能的情况包括:
(1)第i-1天处于“买入两次,卖出一次”状态,第i天卖出。对应利润为buy2(i-1)+pirces[i]
(2)第i-1天处于“买入两次,卖出两次”状态,第i天无操作。对应利润为sell2(i-1)。
因此有sell2(i) = max(buy2(i-1)+pirces[i],sell2(i-1))。
总结状态转移方程为:
buy1(i) = max(-pirces[i],buy1(i-1))
sell1(i) = max(buy1(i-1)+pirces[i],sell1(i-1))
buy2(i) = max(sell1(i-1)-pirces[i],buy2(i-1))
sell2(i) = max(buy2(i-1)+pirces[i],sell2(i-1))
base case:
buy1的初始值表示第0天只买入一次,因此利润为-prices[0]。
sell1的初始值可以表示第0天买入一次、卖出一次,因此利润为0。
buy2的初始值可以表示第0天买入两次、卖出一次,因此利润为-prices[0]
sell2的初始值可以表示第0天买入两次、卖出两次,因此利润为0。
Python实现
在代码中需要循环更新四个变量,由于第i天sell2的计算会依赖第i-1天buy2的值,第i天buy2的计算会依赖第i-1天sell1的值,第i天sell1的计算会依赖第i-1天buy1的值,因此在更新时,依次更新sell2、buy2、sell1、buy1。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
#i=0的情况初始化
buy1,sell1,buy2,sell2 = -prices[0],0,-prices[0],0
#这种也更新方式非常好记
for i in range(1,len(prices)):
sell2 = max(buy2+prices[i],sell2)
buy2 = max(sell1-prices[i],buy2)
sell1 = max(buy1+prices[i],sell1)
buy1 = max(-prices[i],buy1)
return sell2
扩展
需要注意的是,无论题目中“在同一天买入,同一天卖出”这种操作是否允许,最终结果都不会受到影响。因为同一天买入、同一天卖出操作的收益为0。
由于buy1(i) = max(-pirces[i],buy1(i-1)),因此buy1(i)+prices[i] = max(buy1(i-1)+prices[i],-prices[i]+prices[i])。-prices[i]+prices[i] 表示在第i天买入,又在第i天卖出,不会带来额外的收益,因此不会对结果产生影响。另外从表达意义上看,buy1(i)和buy1(i-1)相比,多考虑了一种在第i天买入股票的情况,但是在计算sell1状态时,考虑了在第i天卖出股票的情况。因此,即便是用buy1(i)代替buy1(i-1),多考虑的“在第i天买入股票”的情况和在第i天卖出股票的情况同时出现,收益为0,因此不会对结果造成任何影响。
同样的,buy2(i)也可以在sell1(i)的基础上更新,sell2(i)也可以在buy2(i)的基础上更新。
因此状态转移方程可以写为
buy1(i) = max(-pirces[i],buy1(i-1))
sell1(i) = max(buy1(i)+pirces[i],sell1(i-1))
buy2(i) = max(sell1(i)-pirces[i],buy2(i-1))
sell2(i) = max(buy2(i)+pirces[i],sell2(i-1))
Python实现
class Solution:
def maxProfit(self, prices: List[int]) -> int:
buy1,sell1,buy2,sell2 = -prices[0],0,-prices[0],0
for i in range(1,len(prices)):
buy1 = max(-prices[i],buy1)
sell1 = max(buy1+prices[i],sell1)
buy2 = max(sell1-prices[i],buy2)
sell2 = max(buy2 + prices[i],sell2)
return sell2
参考
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/discuss/39611/Is-it-Best-Solution-with-O(n)-O(1).