株式を売買するのに最適な時期 IV

方法 1: 動的プログラミングの
アイデアとアルゴリズム

他の株式問題と同様に、一連の変数を使用して「買い」ステータスを保存し、次に一連の変数を使用して「売り」ステータスを保存します。この問題は動的計画法によって解決できます。

この場合、配列内の価格に対する正確に jj 個の取引の最大利益を\textit{購入}[i][j]購入[i][j]表すためにを使用し、現在手元に株式を保有しています。; を使用して、正確に jj 個の取引を表し、現在手元に株式を持っていません。この場合。\textit{価格}[0..i]価格[0..i]\textit{販売}[i][j]販売[i][j]

その後、状態遷移方程式を導き出すことができます。\textit{buy}[i][j]buy[i][j] については、現在保有している株が ii 日目に購入されたかどうかを検討します。ii 日目に購入された場合、i-1i−1 日目には株式は保持されず、対応する状態は \textit{sell}[i-1][j]sell[i-1][j] になります。 ]、\textit{prices}[i]prices[i] の購入コストを差し引く必要があります。ii 日目に購入されなかった場合、i-1i−1 日目に在庫を手元に保持します。 \textit{buy}[i-1][j]buy[i−1][j] ステータスにします。次に、状態遷移方程式を取得できます。

\textit{購入}[i][j] = \max \big\{ \textit{購入}[i-1][j], \textit{販売}[i-1][j] - \textit{価格}[i] \big\}

buy[i][j]=max{buy[i−1][j],sell[i−1][j]−price[i]}

同様に、\textit{販売}[i][j]販売[i][j],ii 日目に売却された場合、i-1i−1 日目に在庫を手元に保持します。これは \textit{buy}[i-1][j-1]buy[ の状態に対応します。 i− 1][j−1]、そして \textit{prices}[i]prices[i] の販売利益を増やす必要があります; ii 日に売れなかった場合は、i-1i−1 に株式を保有していない場合、対応する状態は \textit{sell}[i-1][j]sell[i−1][j] になります。次に、状態遷移方程式を取得できます。

\textit{売り}[i][j] = \max \big\{ \textit{売り}[i-1][j], \textit{買い}[i-1][j-1] + \textit {価格}[i] \big\}

売る[i][j]=max{売る[i−1][j],買う[i−1][j−1]+価格[i]}

すべての nn 日が経過した後、株式を保有していない場合の最大利益は、厳密には株式を保有している場合の最大利益によるものでなければなりません。ただし、完了したトランザクションの数が常に優れているとは限りません (たとえば、配列は\textit{価格}価格単調減少しており、トランザクションを行わない場合が最適です)ので、最終的な答えは の\textit{販売}[n-1][0..k]販売[n−1][0..k]最大値になります。

詳細

上記の状態遷移方程式では、境界条件を決定することが非常に重要なステップです。\textit{購入}[0][0..k]購入[0][0..k]すべてのと を境界として設定することを検討できます\textit{販売}[0][0..k]販売[0][0..k]

\textit{価格}[0]価格[0]\textit{buy}[0][0..k]buy[0][0..k] の場合、一意の株価しか存在しないため、取引はできません。その後、すべてを\textit{購入}[0][1..k]購入[0][1..k]1 つの A に設定できます。値が小さい場合は、不正な状態を示します。そして\textit{購入}[0][0]購入[0][0]、 の場合、その値は - です。つまり、「 -\textit{価格}[0]−価格[0]00 日目の\textit{価格}[0]価格[0]価格で株を購入した」ということが、株を保持する必要性を満たす唯一の方法です。

\textit{sell}[0][0..k]sell[0][0..k] の場合、同様にすべての \textit{sell}[0][1..k]sell[0 ][ 1..k] は非常に小さな値に設定されており、不正な状態を示しています。\textit{sell}[0][0]sell[0][0] の場合、その値は 00 です。つまり、「00 日目には何もしない」が株を保有しないという要件を満たす唯一の方法です。 . .

境界を設定した後、i\in [1,n), j \in [0, k]i∈[1,n),j∈[0,k] の範囲内で二重ループを使用できます。実行状態移行。\textit{sell}[i][j]sell[i][j] の状態遷移方程式には \textit{buy}[i-1][j-1]buy[i−1] が含まれることに注意してください。 [j−1]、j=0j=0 の場合は不正な状態を表すため、j=0j=0 の場合は \textit{sell}[i][j]sell[i][j] する必要はありません。 00のままになるように転送してください。

最後に注意すべきことは、この質問における kk の最大値は 10^910 に達する可能性があるということです。 

コード

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if (prices.empty()) {
            return 0;
        }

        int n = prices.size();
        k = min(k, n / 2);
        vector<vector<int>> buy(n, vector<int>(k + 1));
        vector<vector<int>> sell(n, vector<int>(k + 1));

        buy[0][0] = -prices[0];
        sell[0][0] = 0;
        for (int i = 1; i <= k; ++i) {
            buy[0][i] = sell[0][i] = INT_MIN / 2;
        }

        for (int i = 1; i < n; ++i) {
            buy[i][0] = max(buy[i - 1][0], sell[i - 1][0] - prices[i]);
            for (int j = 1; j <= k; ++j) {
                buy[i][j] = max(buy[i - 1][j], sell[i - 1][j] - prices[i]);
                sell[i][j] = max(sell[i - 1][j], buy[i - 1][j - 1] + prices[i]);   
            }
        }

        return *max_element(sell[n - 1].begin(), sell[n - 1].end());
    }
};


状態遷移方程式では、\textit{buy}[i][j]buy[i][j] と \textit{sell}[i][j]sell[i][j] はどちらも \textit から始まることに注意してください。 { buy}[i-1][..]buy[i−1][..] と \textit{sell}[i-1][..]sell[i−1][..] が転送されます。したがって、状態転送には 2 次元配列の代わりに 1 次元配列を使用できます。

\begin{cases} b[j] \leftarrow \max \big\{ b[j], s[j] - \textit{price}[i] \big\} \\ \\ s[j] \leftarrow \ max \big\{ s[j], b[j-1] + \textit{price}[i] \big\} \end{cases}
  
b[j]←max{b[j],s[j]−価格[i]}
s[j]←max{s[j],b[j−1]+価格[i]
}
 

このような状態遷移方程式は、状態の範囲によって異なります。たとえば、\textit{b}b最初に ss を計算し、次に ss を計算すると、s[j]s[j] が計算されるとき、状態遷移方程式に含まれる b[j-1]b[j−1] 項目の値は次のようになります。つまり、\textit{購入}[i-1][j-1]購入[i−1][j−1]2 次元表現から転送されるべきでしたが、現在は\textit{購入}[i-1][j-1]購入[i−1][j−1]

しかし、それはまだ正しいのです。\textit{buy}[i][j-1]buy[i][j−1] の状態遷移方程式を考えます。

b[j-1] \leftarrow \textit{買う}[i][j-1] = \max \big\{ \textit{買う}[i-1][j-1], \textit{売る}[ i-1][j-1] - \textit{価格}[i] \big\}
b[j−1]←買い[i][j−1]=max{買い[i−1][j−1],売り[i−1][j−1]−価格[i]}

この場合、s[j]s[j] の状態遷移方程式は実際には次のようになります。

s[j] \leftarrow \max \big\{ s[j], \textit{買う}[i-1][j-1] + \textit{価格}[i], \textit{売る}[i- 1][j-1] \big\}
s[j]←max{s[j],buy[i−1][j−1]+prices[i],sell[i−1][j−1 ]}

\textit{sell}[i-1][j-1]sell[i−1][j−1] という用語が s[j]s[j] の状態遷移方程式に現れるのはなぜですか? 実際には、「ii 日目に \textit{prices}[i]prices[i] で購入し、同日に \textit{prices}[i]prices[i] で販売する」とすると取引とみなされますので、対応する収入は次のとおりです。

\textit{販売}[i-1][j-1] - \textit{価格}[i] + \textit{価格}[i]
販売[i−1][j−1]−価格[i]+価格[i]

つまり、 \textit{sell}[i-1][j-1]sell[i−1][j−1] 自体です。同じ日内の取引のこの場合、収入はゼロです。追加の収入はもたらさないため、最終的な答えには影響しません。状態遷移方程式は本質的に正しいままです。

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if (prices.empty()) {
            return 0;
        }

        int n = prices.size();
        k = min(k, n / 2);
        vector<int> buy(k + 1);
        vector<int> sell(k + 1);

        buy[0] = -prices[0];
        sell[0] = 0;
        for (int i = 1; i <= k; ++i) {
            buy[i] = sell[i] = INT_MIN / 2;
        }

        for (int i = 1; i < n; ++i) {
            buy[0] = max(buy[0], sell[0] - prices[i]);
            for (int j = 1; j <= k; ++j) {
                buy[j] = max(buy[j], sell[j] - prices[i]);
                sell[j] = max(sell[j], buy[j - 1] + prices[i]);   
            }
        }

        return *max_element(sell.begin(), sell.end());
    }
};


複雑さの分析

時間計算量: O(n\min(n, k))O(nmin(n,k))、nn は配列 \textit{prices}prices のサイズ、つまり、使用するのにかかる時間です。動的プログラミングの二重ループ。

空間計算量: O(n\min(n, k))O(nmin(n,k)) または O(\min(n, k))O(min(n,k))、2 つの使い方に応じて動的プログラミング用の次元配列または 1 次元配列。

                                                                                                                                                         

おすすめ

転載: blog.csdn.net/aliyonghang/article/details/128328280