アルゴリズム ルーチン 17 - 株の売買問題: ステート マシン DP

アルゴリズム ルーチン 17 - 株の売買問題: ステート マシン DP

ステートマシンDPは、有限状態マシン(Finite State Machine)に動的計画法を応用した問題解決手法です。
State Machine DP (State Machine DP) は動的プログラミングのアイデアであり、通常は状態遷移に関するいくつかの問題を解決するために使用されます。ステート マシン DP では、問題をステート マシンに抽象化します。ステート マシンでは、各状態が問題のサブ問題を表し、各状態間に状態遷移があり、あるサブ問題から別のサブ問題に移行するプロセスを表します。 。ステート マシン DP メソッドは、文字列のマッチングやシーケンスの比較など、複数のサブ問題間の依存関係が関係する問題にも適しています。
株の売買問題は、典型的なステートマシンの DP 問題です。「株を保有していない」状態と「株を保有している」という 2 つの状態があり、各ノードは、ある状態における最大の収入を表し、隣接するノード間のエッジは、現在の状態で取引を実行することによって得られる収入。

ここに画像の説明を挿入

アルゴリズム例 1: LeetCode122. 株式の売買に最適な時期 II

整数配列priceが与えられます。price[i]はi日目の特定の株式の価格を表します。
毎日、株を買うか売るかを決定できます。一度に保有できる株式は 1 株までです。先に購入して同日に販売することも可能です。
あなたが得ることができる最大の利益を返します。
ここに画像の説明を挿入

方法 1: 再帰 + メモリ検索

  1. 再帰関数:dfs(i, hold)i日目に株式を保有することで得られる最大リターンを示します。Hold は在庫があることを示す場合は true、在庫がないことを示す場合は false
  2. 伝達方程式:
    • iiの場合i日間株式を保有する場合、現在の最大収益は 2 つの可能な譲渡から得られます。
      • 在第 i − 1 i - 1 株式を1日間保有し、売らないことを選択した場合: このときの収入はdfs ( i − 1 , True ) dfs(i - 1, True)df s (1 本当です _ _
      • 在第 i − 1 i - 1 株式を1日間保有せずに購入を選択した場合: このときのリターンは、dfs ( i − 1 , F alse ) − 価格 [ i ] dfs(i - 1, False) - 価格 [i] となります。df s (1 誤りです_ _価格[ i ] 、現在の利益から今日の価格を引いたもの
      • 両者の最大値を転送結果とします。
    • iiの場合在庫がi日間保持されない場合、現在の最大収益は 2 つの可能性から計算されます。
      • 在第 i − 1 i - 1 株式を1日間保有せず、購入しないことを選択した場合: この時点のリターンはdfs ( i − 1 , F alse ) dfs(i - 1, False)df s (1 ) _ _ _
      • 在第 i − 1 i - 1 株式を1日間保有し、売却を選択した場合: この時点のリターンはdfs ( i − 1 , True ) + 価格 [ i ] dfs(i - 1, True) + 価格 [i]df s (1 本当です_ _+価格[ i ] 、現在在庫ないため資金は利用可能です
      • 両者の最大値を転送結果とします。
        ここに画像の説明を挿入
  3. 境界値: すべてが通過されたとき、つまり i<0 の場合、株式を保有している場合は負の無限大 (不正な状態を表す) を返し、それ以外の場合は 0 を返します。
  4. 戻り値: dfs(n - 1, False)
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices) 
        @cache  # 使用缓存装饰器,加速递归函数计算
        # i 表示当前考虑到的股票价格下标,hold 表示当前是否持有股票
        def dfs(i: int, hold: bool) -> int:
            if i < 0:  # 如果已经遍历完所有股票价格
                return -inf if hold else 0  # 如果持有股票,返回负无穷(代表不合法状态);否则返回零利润。
            # 如果当前已经持有一支股票 
            if hold:      
                #卖或不卖    
                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, False) 

方法 2: 2 次元 dp 配列の動的プログラミング

上記の再帰+記憶検索コードを直接使用して動的プログラミングに変換
dp[i][0] は i 日目以降に在庫がないことを意味します dp[i][1] は i 日目以降を意味します日 株式には最大リターンがあり、最終日に手持ちの株式をすべて売却した後の最大リターンを示す dp[n][0] が返されます。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)  
        dp = [[0] * 2 for _ in range(n + 1)]
        dp[0][1]=-inf
        for i, price in enumerate(prices):
            #当前没有股票,只能不买或卖
            dp[i+1][0]=max(dp[i][0],dp[i][1]+price)
             #当前有股票,只能买或不卖
            dp[i+1][1]=max(dp[i][0]-price,dp[i][1])
        return dp[n][0]

アルゴリズム演習 1: LeetCode714. 株式の売買に最適な時期には手数料が含まれます

整数配列のpricesを指定すると、price[i]はi日目の株価を表し、整数の手数料は株式取引の手数料を表します。
取引は無制限に完了できますが、取引ごとに取引手数料を支払う必要があります。すでに株を購入している場合は、売却するまで別の株を購入し続けることはできません。
利益の最大値を返します。
注: ここでの取引とは、株式の購入、保有、売却のプロセス全体を指し、各取引ごとに手数料のみを支払う必要があります。
ここに画像の説明を挿入

考え方は例と同じですが、購入時に株価だけでなく手数料も差し引く必要があります。

func maxProfit(prices []int, fee int) int {
    
    
    n := len(prices)
    dp := make([][]int, n+1)
    for i := range dp {
    
    
        dp[i] = make([]int, 2)
    }
    dp[0][1] = -math.MaxInt32
    for i, price := range prices {
    
    
    	//不买或卖
        dp[i+1][0] = max(dp[i][0], dp[i][1]+price)
        //买并支付小费
        dp[i+1][1] = max(dp[i][0]-price-fee, dp[i][1])
        
    }
    return dp[n][0]
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

アルゴリズム演習 2: LeetCode309. 株式の売買に最適な時期には凍結期間が含まれます

整数配列の 価格 を指定します。ここで、prices[i] は i 日目の株価を表します。最大利益を計算するアルゴリズムを設計します。次の制約の下では、できるだけ多くのトランザクション (株式の売買を複数回行う) を完了できます。 株式を売却した
後、翌日には株式を購入することはできません (つまり、凍結期間は 1 日です)。
注: 同時に複数の取引に参加することはできません (再度購入する前に、前の株式を売却する必要があります)。
ここに画像の説明を挿入

この質問と例の唯一の違いは、追加の凍結期間があることです。これは株式の購入状況にのみ影響します。凍結期間により、株式の購入状況を計算する場合、わずか 2 日前で株式はありません。手持ちのアイテムは使用可能で、初日終了時には前日の購入がないため、初日は別途対応する必要があります。

func maxProfit(prices []int) int {
    
    
    n := len(prices)
    dp := make([][]int, n+1)
    for i := range dp {
    
    
        dp[i] = make([]int, 2)
    }
    //-math.MaxInt32表示不符合现实
    dp[0][1] = -math.MaxInt32
    for i, price := range prices {
    
    
        //当前没有股票,只能不买或卖
        dp[i+1][0] = max(dp[i][0], dp[i][1]+price)
        //当前有股票,只能买或不卖
        if i>0{
    
    
        //i>0时,对于买的情况,由于冷冻期所以只能用dp[i-1][0]即2天前且手上没有股票的最大利润
        dp[i+1][1] = max(dp[i-1][0]-price, dp[i][1])
        }else {
    
    
            //i=0即第一天想要有股票只能买
            dp[i+1][1] =-price
        }
    }
    return dp[n][0]
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

アルゴリズム演習 3: LeetCode123. 株式の売買に最適な時期 III

i 番目の要素が i 日目の特定の株式の価格である配列が与えられたとします。
得られる最大利益を計算するアルゴリズムを設計します。最大 2 つのトランザクションを完了できます。
注: 同時に複数の取引に参加することはできません (再度購入する前に、前の株式を売却する必要があります)。
ここに画像の説明を挿入

再帰的 + メモ化された検索

最初に再帰 + メモリ検索について考えてアイデアを発展させます。パラメータ j を再帰に追加して現在の走査回数を記録できることは簡単にわかります。コードは次のとおりです。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        # 使用cache装饰器来实现记忆化搜索
        @cache
        def dfs(i: int, j: int, hold: bool) -> int:
            # 如果j小于0,表示交易次数已经用完,返回负无穷
            if j < 0:
                return -inf
            # 如果i小于0,表示已经到达第0天,如果持有股票,返回负无穷,否则返回0
            if i < 0:
                return -inf if hold else 0
            if hold:
                return max(dfs(i - 1, j, True), dfs(i - 1, j - 1, False) - prices[i])
            else:
                return max(dfs(i - 1, j, False), dfs(i - 1, j, True) + prices[i])
        return dfs(n - 1, 2, False)

動的プログラミング

動的プログラミングに変換された上記の再帰的プロセスによれば、再帰的関数はパラメーターを追加するため、動的配列 dp に 1 次元トランザクションの数を追加します。ここで dp[i][j][0] は i 番目のトランザクションを表します。 dp[i][j][1] は、i 日目、最大 j トランザクションで、現在株式の最大収益率を保持していることを表します。

ただし、新規取引としてカウントされるのは買いの場合のみで、売りは新規取引としてカウントされないので注意が必要です。

func maxProfit(prices []int) int {
    
    
    n := len(prices)
    dp := make([][][2]int, n+1)
    for i:=range dp{
    
    
        dp[i]=make([][2]int,3)
        for j:=0;j<3;j++{
    
    
            dp[i][j][1] = math.MinInt32/2
        }
    }
    for i:=0;i<n;i++{
    
    
        for j:=0;j<2;j++{
    
    
            //不买或卖
            dp[i+1][j+1][0]=max(dp[i][j+1][0],dp[i][j+1][1]+prices[i])
            //不卖或买,买即增加一次交易
            dp[i+1][j+1][1]=max(dp[i][j+1][1],dp[i][j][0]-prices[i])
        }
    }
    return dp[n][2][0]
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

アルゴリズム演習 4: LeetCode188. 株式を売買する最適な時期 IV

整数配列priceを指定すると、そのi番目の要素price[i]は、i日目の特定の株式の価格と整数kになります。
得られる最大利益を計算するアルゴリズムを設計します。最大 k 件のトランザクションを完了できます。言い換えれば、最大 k 回買うことができ、最大 k 回売ることができます。
注: 同時に複数の取引に参加することはできません (再度購入する前に、前の株式を売却する必要があります)。
ここに画像の説明を挿入

上記の質問のアイデアを直接採用し、j の範囲を 0-2 から 0-k に変更します。

func maxProfit(k int, prices []int) int {
    
    
    n := len(prices)
    dp := make([][][2]int, n+1)
    for i:=range dp{
    
    
        dp[i]=make([][2]int,k+1)
        for j:=0;j<k+1;j++{
    
    
            dp[i][j][1] = math.MinInt32/2
        }
    }
    for i:=0;i<n;i++{
    
    
        for j:=0;j<k;j++{
    
    
            //不买或卖
            dp[i+1][j+1][0]=max(dp[i][j+1][0],dp[i][j+1][1]+prices[i])
            //不卖或买,买即增加一次交易
            dp[i+1][j+1][1]=max(dp[i][j+1][1],dp[i][j][0]-prices[i])
        }
    }
    return dp[n][k][0]
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

アルゴリズム演習 5: LeetCode1911. 最大サブシーケンス交互合計

0 から始まるインデックスを持つ配列の交互の合計は、偶数のインデックスの要素の合計から奇数のインデックスの要素の合計を引いたものとして定義されます。
たとえば、配列 [4,2,5,3] の交互の合計は (4 + 5) - (2 + 3) = 4 となります。
配列 nums を指定すると、nums 内のサブシーケンスの最大交互合計を返してください (サブシーケンスの添え字は 0 から番号付けされます)。
配列のサブシーケンスとは、元の配列から一部の要素が削除された (またはまったく削除されなかった) 後も、残りの要素の順序が変更されない配列です。たとえば、[2,7,4] は [4,2,3,7,2,1,4] のサブシーケンス (太字の要素) ですが、[2,4,2] はそうではありません。
ここに画像の説明を挿入

株の売買に似ていますが、違いは、この質問の開始時に株が 0 元で購入されているため、初期化配列 dp[0][1] = 0、dp[0][0] = math となることです。 MinInt64 / 2、その他は例のアイデアと同じ

func maxAlternatingSum(nums []int) int64 {
    
    
    //类比买卖股票,即第一次0元买进,之后卖出
    n := len(nums)
    dp := make([][]int, n+1)
    for i := range dp {
    
    
        dp[i] = make([]int, 2)
    }
    //负无穷表示不符合题意,/2防止越界
    dp[0][0] =math.MinInt64 / 2
    //开始0元买进了股票
    dp[0][1] =0
    for i, num := range nums {
    
    
        dp[i+1][0] = max(dp[i][0], dp[i][1]+num)
        dp[i+1][1] = max(dp[i][0]-num, dp[i][1])
    }
    return int64(dp[n][0])
}
func max(x, y int) int {
    
    if x > y {
    
    return x};return y}

おすすめ

転載: blog.csdn.net/qq_45808700/article/details/130490058