Coding on 状态机DP

Example leetcode 买卖股票问题

Posted by Kylin on October 31, 2023

[TOC]

不限交易次数

eg. Leetcode122

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润

Solution:

1)先画状态机:

image-20231101105558419

2)给出两个状态的转移方程: $$ dfs(i,0) = max(dfs(i-1,0),dfs(i-1,1)+prices[i])\

dfs(i,1) = max(dfs(i-1,1),dfs(i-1,0)-prices[i]) $$

3)递归边界 \(dfs(-1,0) = 0 \ \ \ \ \ // 第0天开始时候如果未持有股票,则利润为0 \\ dfs(-1,1) = -10**9-7 \ \ \ \ \ //第0天开始不可以持有股票\)

4)递归入口 \(max(dfs(n-1,0),dfs(n-1,1))=dfs(n-1,0)\)

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

        @lru_cache(maxsize=None)
        def dfs(i:int,s:bool)->int:
            if i<0:
                return -inf if s else 0
            if s:
                return max(dfs(i-1,True),dfs(i-1,False)-prices[i])
            return max(dfs(i-1,False),dfs(i-1,True)+prices[i])

        return dfs(n-1,0)

5)翻译成递推 \(f[0][0] = 0, f[0][1] = -inf \\ f[i+1][0] = max(f[i][0],f[i][1]+prices[i])\\ f[i+1][1] = max(f[i][1],f[i][0]-prices[i])\\ return \ \ f[n][0]\) 6)空间优化-迭代

需要考虑到dp0还需要被用于更新dp1,不要将其覆盖掉

\[dp0 = 0, dp1 = -inf \\ new\_dp0 = max(dp0,dp1+prices[i])\\ dp1 = max(dp1,dp0-prices[i])\\ dp0 = new\_dp0\\ return \ \ dp0\]

7)终极空间优化-覆盖

一般来讲,可以找到一个更新顺序,是得覆盖合理

比如这里dp0被更新之后,被用于dp1更新也是合理的,无非当天买入当天卖出

\[dp0 = 0, dp1 = -inf \\ dp0 = max(dp0,dp1+prices[i])\\ dp1 = max(dp1,dp0-prices[i])\\ return \ \ dp0\]

8)奇怪的优化-python

其实可以直接使用python的多元素赋值,直接解决问题

\[dp0 = 0, dp1 = -inf \\ dp0,dp1 = max(dp0,dp1+prices[i]),max(dp1,dp0-prices[i])\\ return \ \ dp0\]
eg. Leetcode309

给定一个整数数组prices,其中第 prices[i] 表示第 *i* 天的股票价格 。

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

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

Solution:

1)状态机

三个状态:买入dp0,未买入dp1,冷冻dp2 \(dp0,dp1,dp2 = -inf,0,-inf\\ dp0,dp1,dp2 = max(dp0,dp1-price),max(dp1,dp2),dp0+price\\ return\ \ max(dp1,dp2)\)

3)优化

其实可以直接两个状态转移, 买入股票的状态之内从两天前的未持有状态转移而来

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

        @lru_cache(maxsize=None)
        def dfs(i:int,s:bool)->int:
            if i<0:
                return -inf if s else 0
            if s:
                return max(dfs(i-1,True),dfs(i-2,False)-prices[i])
            return max(dfs(i-1,False),dfs(i-1,True)+prices[i])

        return dfs(n-1,0)

至多/至少/恰好交易k次

eg. Leetcode188

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

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

Solution:

1)状态机

image-20231101142844434

2)转移方程 \(dfs(i,j,0) = max(dfs(i-1,j,0),dfs(i-1,j,1)+price[i]) \\ dfs(i,j,1) = max(dfs(i-1,j,1),dfs(i-1,j-1,0)-price[i])\)

3)递归边界 \(dfs(\cdot,-1,\cdot) = -inf \ \ \ \ \ // 任何情况下,交易次数不可能为负 \\ dfs(-1,j,0) = 0 \ \ \ \ \ // 第0天开始时候如果未持有股票,则利润为0 \\ dfs(-1,j,1) = -inf \ \ \ \ \ //第0天开始不可以持有股票\)

4)递归入口 \(max(dfs(n-1,k,0),dfs(n-1,k,1)) = dfs(n-1,k,0)\)

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

        @lru_cache(maxsize=None)
        def dfs(i:int,j:int,s:bool)->int:
            if j<0:
                return -inf
            if i<0:
                return -inf if s else 0
            if s:
                return max(dfs(i-1,j,True),dfs(i-2,j-1,False)-prices[i])
            return max(dfs(i-1,j,False),dfs(i-1,j,True)+prices[i])

        return dfs(n-1,k,0)

5)翻译为递推 \(f[\cdot][0][\cdot] = -inf, \ \ f[0][j][0] = 0,\ \ f[0][j][1] = -inf \\ f[i+1][j+1][0] = max(f[i][j+1][0],f[i][j+1][1]+prices[i])\\ f[i+1][j+1][1] = max(f[i][j+1][1],f[i][j][0]-prices[i])\\ return \ \ f[n][k+1][0]\)

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

        f = [[-inf]*2 for _ in range(k+2)]
        for i in range(1,k+2):
            f[i][0] = 0

        for i,p in enumerate(prices):
            for j in range(k,-1,-1):
                f[j+1][0] = max(f[j+1][0],f[j+1][1]+p)
                f[j+1][1] = max(f[j+1][1],f[j][0]-p)
        return f[k+1][0]
Follow up: 恰好k次

初始化改为: \(f[0][1][0] = 0\\ others = -inf\) 因为递归到 $i<0$ 时,只有 $j=0$ 才是合法的,$j>0$ 是不合法的。

Follow up: 至少k次

初始化改成: \(f[0][0][0] = 0,\ \ others = -inf \\ f[i+1][0][0] = max(f[i][0][0],f[i][0][1]+prices[i])\\ f[i+1][0][1] = max(f[i][0][1],f[i][0][0]-prices[i])\\ return \ \ f[n][k+1][0]\) 递归到「至少 0 次」时,它等价于「交易次数没有限制」

Leetcode 题单