leetcode 股票交易问题动态规划一网打尽

**

通用动态规划状态分解法方法解决股票交易最大利润问题

1. 算法框架

状态的三个维度
天数:[0, 1,…,N - 1]
交易次数(一次交易对应买+卖两个操作): [0, 1,…,k]
手上是否还持有股票:[0,1]

通用要求解答案:max(dp[N ][_][0] for _ in range(k + 1))

动作集合:买入(交易次数+1),卖出,无操作

状态转移方程
注:交易次数按照一次买入时增加,因为买入和卖出是成对出现,要先买才能卖

dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1] + price[i])
#第i天未持有股票的最大利润 = max(第i-1天未持有股票的最大利润, 第i-1天持有股票的最大利润 + 在第i天卖出股票得到的钱)

dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - price[i])
#第i天持有股票的最大利润 = max(第i-1天持有股票的最大利润, 第i-1天未持有股票的最大利润 - 在第i天买入股票花的钱)

状态初始化

# j=0 交易次数为0
for i in range(n):
    dp[i][0][0] = 0		# 没有买入过股票,且手头没有持股,则获取的利润为0
    dp[i][0][1] = float('-inf')	# 没有买入过股票,不可能持股,用利润负无穷表示这种不可能
    
# i=0 天数为0
#正确做法
for j in range(1, k+1):	
# i = 0 时,除了[0][1][1]其他状态都不存在,交易次数不可能大于1
    dp[0][k][0] = float('-inf')	
    dp[0][k][1] = float('-inf')
dp[0][1][1] = - prices[0]

#看到leetcode上有些小伙伴的做法是为了缩减状态会将i=0时的情况写成如下:
for j in range(1, k+1):	
	dp[i][j][1] = - prices[i]
	dp[i][j][0] = 0
#不会影响最终结果,但是仔细研究的话中间会有状态的值是错误的
            

2. 具体解法

股票系列一共 6 道题均可以利用上面的通用方法进行求解,只是细节上有一些变化:

  • LeetCode 121:最多进行 1 笔交易(k=1)【贪心】
  • LeetCode 122:不限交易次数(k=+inf)【二维 DP】
      LeetCode 309:不限交易次数(k=+inf),但有「冷冻期」的额外条件
      LeetCode 714:不限交易次数(k=+inf),但有「手续费」的额外条件
  • LeetCode 188:最多进行 k 次交易 【三维 DP】
      LeetCode 123:最多进行 2 笔交易(k=2)
LeetCode 121类:最多进行 1 笔交易【贪心】

我们只要记录当前交易日前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,决定是否更新当前的最大收益。注意维护两个变量:前面的最小价格 和 当前的最大收益。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if len(prices)<=1: 
        	return 0
        buy, profit = float('inf'), 0  # 买入值 和 利润
        for curr in prices:
            profit = max(profit, curr-buy)
            buy = min(curr, buy)
        return profit
LeetCode 122类:不限交易次数(k=+inf)【二维 DP】

方法1:贪心
找到股票每一次升落都买入卖出

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if len(prices)<=1: 
        	return 0
        profit = 0
        for i in range(1, len(prices)):
            if prices[i] > prices[i-1]:
                profit += prices[i] - prices[i-1]
        return profit

方法2:约简通用dp算法
由于交易次数为无穷不能再影响我们的dp,所以将其从状态方程中约去

dp[i]0] = max(dp[i - 1][0], dp[i - 1][1] + price[i])
#第i天未持有股票的最大利润 = max(第i-1天未持有股票的最大利润, 第i-1天持有股票的最大利润 + 在第i天卖出股票得到的钱)

dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - price[i])
#第i天持有股票的最大利润 = max(第i-1天持有股票的最大利润, 第i-1天未持有股票的最大利润 - 在第i天买入股票花的钱)

边界状态约简为:

# i=0 天数为0
dp[0][0] = 0	
dp[0][1] = - prices[0]            

代码:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if len(prices)<=1: 
        	return 0

        dp = [[0, 0] for _ in range(n)]
        dp[0][0] = 0
        dp[0][1] = -prices[0]

        for i in range(1, n):
            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]  

引申: LeetCode 309:增加「冷冻期」的额外条件
状态方程改变买入股票时候要考虑冷冻期

dp[i]0] = max(dp[i - 1][0], dp[i - 1][1] + price[i])
#第i天未持有股票的最大利润 = max(第i-1天未持有股票的最大利润, 第i-1天持有股票的最大利润 + 在第i天卖出股票得到的钱)

dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - price[i])
#第i天持有股票的最大利润 = max(第i-1天持有股票的最大利润, 第i-2天未持有股票的最大利润 - 在第i天买入股票花的钱)

边界状态约简为:

# i=0 天数为0
dp[0][0] = 0	
dp[0][1] = - prices[0] 
dp[1][0] = max( - prices[0] + prices[1], 0)# 第0天持有股票在第一天卖出或者第0,1天无操作
dp[1][1] = max(- prices[0], - prices[1]) # 第0天持有股票或者在第1天买入

代码:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if len(prices<=1: 
        	return 0

        dp = [[0, 0] for _ in range(n)]
        dp[0][0] = 0
        dp[0][1] = -prices[0]
        dp[1][0] =  max( - prices[0] + prices[1], 0) 
        dp[1][1] =  max(- prices[0], - prices[1])

        for i in range(2, n):
            dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
            dp[i][1] = max(dp[i-1][1], dp[i-2][0]-prices[i])   
        return dp[-1][0]

引申: LeetCode 714:增加「手续费」的额外条件
状态方程改变买入股票时候要考虑手续费
在卖出时候减手续费初始状态较122不需要改变
但注意若在买入时减fee的话初始状态 dp[0][1] = - prices[0] - fee

dp[i]0] = max(dp[i - 1][0], dp[i - 1][1] + price[i] -fee)
#第i天未持有股票的最大利润 = max(第i-1天未持有股票的最大利润, 第i-1天持有股票的最大利润 + 在第i天卖出股票得到的钱 - fee)

dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - price[i])
#第i天持有股票的最大利润 = max(第i-1天持有股票的最大利润, 第i-2天未持有股票的最大利润 - 在第i天买入股票花的钱)

边界状态教122未改变:

# i=0 天数为0
dp[0][0] = 0	
dp[0][1] = - prices[0]            

代码:

class Solution:
     def maxProfit(self, prices: List[int], fee: int) -> int:
        if len(prices)<=1: 
        	return 0

        dp = [[0, 0] for _ in range(n)]
        dp[0][0] = 0
        dp[0][1] = -prices[0]

        for i in range(1, n):
            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]    # 返回最后一天且手上没有股票时的获利情况
LeetCode 188,123类:最多进行 k 次交易 【三维 DP】

这两道题解法通用
不同的是对于188要考虑当 k>N/2 时,就退化成 k = inf 时候的情况

class Solution:
    def maxProfit_k_inf(self, prices):
        if len(prices)<=1: 
        	return 0

        dp = [[0, 0] for _ in range(n)]
        dp[0][0] = 0
        dp[0][1] = -prices[0]

        for i in range(1, n):
            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]
        
    def maxProfit_k(self, k, prices):
        dp = [[[0, float('-inf')] for _ in range(k + 1)] for _ in range(len(prices))]
        for i in range(len(prices)):
            for j in range(1, k + 1):
                if i == 0:
                    if j == 1:
                        dp[0][1][1] = - prices[i]
                    else:
                        dp[i][j][1] = float('-inf')
                        dp[i][j][0] = float('-inf')
                    continue

                dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i])
                dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i])
        return max(dp[len(prices) - 1][_][0] for _ in range(k + 1))

    def maxProfit(self, k: int, prices: List[int]) -> int:
        n = len(prices)

        if k > n / 2:
            return self.maxProfit_k_inf(prices)
        else:
            return self.maxProfit_k(k, prices)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值