1. 程式人生 > >LeetCode123——買賣股票的最佳時機III

LeetCode123——買賣股票的最佳時機III

題目描述:

知識點:動態規劃

列舉第一筆交易賣出的時間firstDealSell,對[0, firstDealSell]範圍內的價格求解LeetCode121——買賣股票的最佳時機中的問題,得到結果result1,再對[firstDealSell, n - 1]範圍內的價格再一次求解LeetCode121——買賣股票的最佳時機中的問題,其中n為prices陣列的大小,得到結果result2,求result1 + result2的最大值。

時間複雜度是O(n ^ 2)。空間複雜度是O(1)。

對於第一次和第二次賣出的時間點,firstDealSell和secondDealSell,其之前的價格一定是一個上坡,其之後的價格一定是一個下坡,我們在價格坡頂賣出。

JAVA程式碼:

public class Solution {
    public int maxProfit(int[] prices) {
        int result = 0;
        if(prices.length == 0){
            return result;
        }
        int firstDealSell;  //第一筆交易在firstDealSell賣出
        int secondDealSell; //第二筆交易在secondDealSell賣出
        for(secondDealSell = prices.length - 1; secondDealSell > 0; secondDealSell--){
            if(prices[secondDealSell - 1] < prices[secondDealSell]){
                break;
            }
        }
        for(firstDealSell = 1; firstDealSell < prices.length; firstDealSell++){
            while(firstDealSell + 1 < prices.length && prices[firstDealSell + 1] >= prices[firstDealSell]){
                firstDealSell++;
            }
            int result1 = maxProfit(prices, 0, firstDealSell);
            int result2 = maxProfit(prices, firstDealSell + 1, secondDealSell);
            if(result1 + result2 > result){
                result = result1 + result2;
            }
        }
        return result;
    }

    private int maxProfit(int[] prices, int left, int right){
        int result = 0;
        if(right - left < 1){
            return result;
        }
        int minPrice = prices[left];
        for(int i = left + 1; i <= right; i++){
            result = Math.max(result, prices[i] - minPrice);
            minPrice = Math.min(minPrice, prices[i]);
        }
        return result;
    }
}

LeetCode解題報告:

思路二:動態規劃

狀態定義:f(x, y) -------- 第x筆交易在第y天能取得的最大利潤

狀態轉移

(1)當x == 1時

        a:我們可以選擇在第y天不賣出也不買入,

                a-1:如果此時y == 0,即第0天,那麼f(1, 0) = 0,即取得的最大利潤為0。

                a-2:如果此時y > 0,那麼此時我們的第x筆交易在第y天能取得的最大利潤是f(1, y - 1),因為我們在第y天既不買入也不賣出,取得的最大利潤自然和第x筆交易在第y - 1天能取得的最大利潤相同。

        b:我們可以選擇在第y天賣出,

                b-1:如果此時y == 0,顯然,我們不可能在第0天賣出,這種情況不予討論。

                b-2:如果此時y > 0,f(x, y) = max(prices[y] - prices[b]),其中0 <= b < y,代表我們在第b天買入。

綜上,對於x == 1的情況,當y == 0時,f(1, 0) = 0;當y > 0時,f(1, y) = max(f(x, y - 1), prices[y] - prices[b]),0 <= b < y

(2)當x == 2時

        a:我們可以選擇在第y天不賣出也不買入,

                a-1:如果此時y == 0,即第0天,那麼f(x, y) = 0,即取得的最大利潤為0。

                a-2:如果此時y > 0,那麼此時我們的第x筆交易在第y天能取得的最大利潤是f(2, y - 1),因為我們在第y天既不買入也不賣出,取得的最大利潤自然和第x筆交易在第y - 1天能取得的最大利潤相同。

        b:我們可以選擇在第y天賣出,

                b-1:如果此時y == 0,顯然,我們不可能在第0天賣出,這種情況不予討論。

                b-2:如果此時y > 0,f(x, y) = max(prices[y] - prices[b] + f(1, b - 1)),其中0 <= b < y,代表我們在第b天買入。

綜上,對於x == 2的情況,當y == 0時,f(2, 0) = 0;當y > 0時,f(2, y) = max(f(x, y - 1), prices[y] - prices[b] + f(1, b - 1)),0 <= b < y

時間複雜度是O(kn ^ 2),其中k為交易次數,n為prices陣列的大小。空間複雜度是O(kn)。

JAVA程式碼:

public class Solution {
    public int maxProfit(int[] prices) {
        int result = 0;
        if(0 == prices.length){
            return result;
        }
        int[][] dp = new int[2][prices.length];
        for(int k = 0; k < 2; k++){
            dp[k][0] = 0;
            int min = prices[0];
            for(int i = 1; i < prices.length; i++){
                for(int b = 1; b < i; b++){
                    if(k == 0){
                        min = Math.min(min, prices[b]);
                    }else{
                        min = Math.min(min, prices[b] - dp[k - 1][b - 1]);
                    }
                }
                dp[k][i] = Math.max(dp[k][i - 1], prices[i] - min);
            }
        }
        return dp[1][prices.length - 1];
    }
}

LeetCode解題報告:

思路三:對思路二的改進

在思路二中,我們對第一筆交易和第二筆交易進行了分別計算,因為第1筆交易的前一筆交易不存在,會產生陣列越界問題。

我們可以在第一筆交易前增加一筆虛擬的第0筆交易,其f(0, y)均為0,這樣就避免了對第一筆交易和第二筆交易的討論。

同時,我們會發現思路二中min的計算其實重複進行了,我們完全可以忽略內層的迴圈變數b的迴圈。

時間複雜度和空間複雜度均是O(kn),其中k為交易次數,n為prices陣列的大小。

JAVA程式碼:

public class Solution {
    public int maxProfit(int[] prices) {
        int result = 0;
        if (0 == prices.length) {
            return result;
        }
        int[][] dp = new int[3][prices.length];
        for(int k = 1; k <= 2; k++){
            dp[k][0] = 0;
            int min = prices[0];
            for(int i = 1; i < prices.length; i++){
                dp[k][i] = Math.max(dp[k][i - 1], prices[i] - min);
                min = Math.min(min, prices[i] - dp[k - 1][i - 1]);
            }
        }
        return dp[2][prices.length - 1];
    }
}

LeetCode解題報告:

思路四:思路三的另一種形式

交換內外層迴圈的位置。

時間複雜度和空間複雜度均是O(kn),其中k為交易次數,n為prices陣列的大小。

JAVA程式碼:

public class Solution {
    public int maxProfit(int[] prices) {
        int result = 0;
        if (0 == prices.length) {
            return result;
        }
        int[][] dp = new int[3][prices.length];
        int[] min = new int[3];
        for(int i = 1; i < 3; i++){
            min[i] = prices[0];
        }
        for(int i = 1; i < prices.length; i++){
            for(int k = 1; k <= 2; k++){
                dp[k][i] = Math.max(dp[k][i - 1], prices[i] - min[k]);
                min[k] = Math.min(min[k], prices[i] - dp[k - 1][i - 1]);
            }
        }
        return dp[2][prices.length - 1];
    }
}

LeetCode解題報告:

思路五:思路四的改進

從思路四中我們發現第i列的變數只依賴於第i - 1列的變數,壓縮該維度。

時間複雜度是O(k),其中k為交易次數,n為prices陣列的大小。空間複雜度是O(k)。

JAVA程式碼:

public class Solution {
    public int maxProfit(int[] prices) {
        int result = 0;
        if (0 == prices.length) {
            return result;
        }
        int[] dp = new int[3];
        int[] min = new int[3];
        for(int i = 1; i < 3; i++){
            min[i] = prices[0];
        }
        for(int i = 1; i < prices.length; i++){
            for(int k = 1; k <= 2; k++){
                dp[k] = Math.max(dp[k], prices[i] - min[k]);
                min[k] = Math.min(min[k], prices[i] - dp[k - 1]);
            }
        }
        return dp[2];
    }
}

LeetCode解題報告:

思路六:思路五的改進

針對本題,只進行2次交易,優化程式碼。

時間複雜度是O(1),其中k為交易次數,n為prices陣列的大小。空間複雜度是O(1)。

JAVA程式碼:

public class Solution {
    public int maxProfit(int[] prices) {
        int buy1 = Integer.MAX_VALUE;
        int sell1 = 0;
        int buy2 = Integer.MAX_VALUE;
        int sell2 = 0;
        for(int i = 0; i < prices.length; i++){
            sell1 = Math.max(sell1, prices[i] - buy1);
            buy1 = Math.min(buy1, prices[i]);
            sell2 = Math.max(sell2, prices[i] - buy2);
            buy2 = Math.min(buy2, prices[i] - sell1);
        }
        return sell2;
    }
}

LeetCode解題報告: