【Lintcode】393. Best Time to Buy and Sell Stock IV

記錄演算法發表於2020-12-12

題目地址:

https://www.lintcode.com/problem/best-time-to-buy-and-sell-stock-iv/

給定一個股票價格的長 n n n的陣列 p p p,至多可以進行 k k k次買賣,同一天只能進行一次買或者賣,問最多得到的利潤是多少。

首先,如果 k ≥ n / 2 k\ge n/2 kn/2,那麼相當於可以進行無限次交易,解法是簡單的。我們考慮 k < n / 2 k< n/2 k<n/2的情形。

思路是狀態機。規定兩個狀態,狀態 1 1 1表示手中有股, 0 0 0表示手中無股。那麼:
1、狀態 1 1 1可以由狀態 1 1 1達到,即昨天手裡有股,然後持有一天到今天仍然有股,則邊權為 0 0 0;狀態 1 1 1可以由狀態 0 0 0達到,即昨天手裡無股,然後今天買入,狀態變為有股,則邊權為 − p [ i ] -p[i] p[i]
2、狀態 0 0 0可以由狀態 0 0 0達到,即昨天手裡無股,然後觀望一天,今天仍然無股,則邊權為 0 0 0;狀態 0 0 0可以由狀態 1 1 1達到,即昨天手裡有股,然後今天賣出,狀態變為無股,則邊權為 + p [ i ] +p[i] +p[i]
而題目相當於是要問,最多允許從兩個狀態轉 k k k圈(表示買入 k k k次賣出 k k k次)的情況下,最大收益是多少。這裡的動態規劃狀態表示並不唯一,在https://blog.csdn.net/qq_46105170/article/details/109007301這篇文章裡, f [ i ] [ j ] f[i][j] f[i][j]表示以有股結尾的,到第 i i i天為止的(這裡 i i i 0 0 0計數),已經執行過最多 j j j次買入操作的情況下的最大收益, g [ i ] [ j ] g[i][j] g[i][j]表示以無股結尾的,到第 i i i天為止的(這裡 i i i 0 0 0計數),已經執行過最多 j j j次買入操作的情況下的最大收益。我們考慮另一種表示,設 f [ i ] [ j ] f[i][j] f[i][j]表示以有股結尾的,到第 i i i天為止的(這裡 i i i 0 0 0計數),恰好執行過 j j j次買入操作的情況下的最大收益, g [ i ] [ j ] g[i][j] g[i][j]表示以無股結尾的,到第 i i i天為止的(這裡 i i i 0 0 0計數),恰好執行過 j j j次買入操作的情況下的最大收益。則有: f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j ] , g [ i − 1 ] [ j − 1 ] − p [ i ] } g [ i ] [ j ] = max ⁡ { g [ i − 1 ] [ j ] , f [ i − 1 ] [ j ] + p [ i ] } f[i][j]=\max\{f[i-1][j],g[i-1][j-1]-p[i]\}\\g[i][j]=\max\{g[i-1][j],f[i-1][j]+p[i]\} f[i][j]=max{f[i1][j],g[i1][j1]p[i]}g[i][j]=max{g[i1][j],f[i1][j]+p[i]}我們發現這個遞推方程其實是一樣的,不一樣的地方在於最後要返回的答案,應該是 max ⁡ j { f [ n − 1 ] [ j ] , g [ n − 1 ] [ j ] } \max_{j}\{f[n-1][j],g[n-1][j]\} maxj{f[n1][j],g[n1][j]}(因為題目條件是最多進行 k k k次交易,只要不超過 k k k次交易的答案都是可以的。這裡不能簡單的返回 max ⁡ { f [ n − 1 ] [ k ] , g [ n − 1 ] [ k ] } \max\{f[n-1][k],g[n-1][k]\} max{f[n1][k],g[n1][k]},例如對於 p = ( 3 , 2 , 1 ) p=(3,2,1) p=(3,2,1) k = 1 k=1 k=1,最好的選擇是不買,但如果強制要求買一次,則是虧錢的),並且初始條件也不一樣。我們考慮 f [ . ] [ 0 ] f[.][0] f[.][0] f [ 0 ] [ 0 ] f[0][0] f[0][0]是不存在的,可以賦值為 − ∞ -\infty 以杜絕從這個狀態轉移過來的解,對於 f [ 1 ] [ 0 ] f[1][0] f[1][0],其表示進行一次交易並且以有股結尾,那麼 f [ 1 ] [ 0 ] = − p [ 0 ] f[1][0]=-p[0] f[1][0]=p[0],而對於 i ≥ 2 i\ge 2 i2,同樣的理由有 f [ i ] [ 0 ] = − ∞ f[i][0]=-\infty f[i][0]=;下面考慮 g [ . ] [ 0 ] g[.][0] g[.][0] g [ 0 ] [ 0 ] = 0 g[0][0]=0 g[0][0]=0,而 i ≥ 1 i\ge 1 i1時, g [ i ] [ 0 ] = − ∞ g[i][0]=-\infty g[i][0]=,理由同上。程式碼實現方面,需要注意的是,java裡負無窮如果減去一個數,會變成正數。所以程式碼裡的負無窮可以用Integer.MIN_VALUE / 2來代替。程式碼如下:

public class Solution {
    /**
     * @param K:      An integer
     * @param prices: An integer array
     * @return: Maximum profit
     */
    public int maxProfit(int K, int[] prices) {
        // write your code here
        int len = prices.length;
        if (K >= len / 2) {
            int res = 0;
            for (int i = 1; i < len; i++) {
                res += Math.max(0, prices[i] - prices[i - 1]);
            }

            return res;
        }
        
        // f[i][j]表示已經進行過i次買入,第j天手裡有股
        // g[i][j]表示已經進行過i次買入,第j天手裡無股
        int[][] f = new int[K + 1][len], g = new int[K + 1][len];
        for (int i = 0; i <= K; i++) {
            f[i][0] = Integer.MIN_VALUE / 2;
        }
        f[1][0] = -prices[0];
        for (int i = 0; i <= K; i++) {
            g[i][0] = Integer.MIN_VALUE / 2;
        }
        g[0][0] = 0;
        
        int res = 0;
        for (int i = 0; i <= K; i++) {
            for (int j = 1; j < len; j++) {
                f[i][j] = f[i][j - 1];
                if (i >= 1) {
                    f[i][j] = Math.max(f[i][j], g[i - 1][j - 1] - prices[j]);
                }
                g[i][j] = Math.max(g[i][j - 1], f[i][j - 1] + prices[j]);
                
                // 更新答案
                if (j == len - 1) {
                	res = Math.max(res, f[i][j]);
                	res = Math.max(res, g[i][j]);
                }
            }
        }
        
        return res;
    }
}

時空複雜度 O ( k n ) O(kn) O(kn)

下面是滾動陣列優化:

public class Solution {
    /**
     * @param K:      An integer
     * @param prices: An integer array
     * @return: Maximum profit
     */
    public int maxProfit(int K, int[] prices) {
        // write your code here
        int len = prices.length;
        if (K >= len / 2) {
            int res = 0;
            for (int i = 1; i < len; i++) {
                res += Math.max(0, prices[i] - prices[i - 1]);
            }
            
            return res;
        }
        
        // f[i][j]表示已經進行過i次買入,第j天手裡有股
        // g[i][j]表示已經進行過i次買入,第j天手裡無股
        int[][] f = new int[2][len], g = new int[2][len];
        f[0][0] = Integer.MIN_VALUE / 2;
        f[1][0] = -prices[0];
        g[0][0] = 0;
        g[1][0] = Integer.MIN_VALUE / 2;
        
        int res = 0;
        for (int i = 0; i <= K; i++) {
            for (int j = 1; j < len; j++) {
                f[i & 1][j] = f[i & 1][j - 1];
                if (i >= 1) {
                    f[i & 1][j] = Math.max(f[i & 1][j], g[i - 1 & 1][j - 1] - prices[j]);
                }
                g[i & 1][j] = Math.max(g[i & 1][j - 1], f[i & 1][j - 1] + prices[j]);
                
                if (j == len - 1) {
                    res = Math.max(res, f[i & 1][j]);
                    res = Math.max(res, g[i & 1][j]);
                }
            }
        }
        
        return res;
    }
}

時間複雜度不變,空間 O ( n ) O(n) O(n)

相關文章