【LeetCode】 Best Time to Buy and Sell Stock I II III IV 解題報告

ljiabin發表於2015-04-06

原文地址:http://liangjiabin.com/blog/2015/04/leetcode-best-time-to-buy-and-sell-stock.html (已失效)

Best Time to Buy and Sell Stock I

題意: 用一個陣列表示股票每天的價格,陣列的第i個數表示股票在第i天的價格。 如果只允許進行一次交易,也就是說只允許買一支股票並賣掉,求最大的收益。

分析: 動態規劃法。從前向後遍歷陣列,記錄當前出現過的最低價格,作為買入價格,並計算以當天價格出售的收益,作為可能的最大收益,整個遍歷過程中,出現過的最大收益就是所求。

程式碼: 時間O(n),空間O(1)。

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

Best Time to Buy and Sell Stock II

題目: 用一個陣列表示股票每天的價格,陣列的第i個數表示股票在第i天的價格。交易次數不限,但一次只能交易一支股票,也就是說手上最多隻能持有一支股票,求最大收益。

分析: 貪心法。從前向後遍歷陣列,只要當天的價格高於前一天的價格,就算入收益。

程式碼: 時間O(n),空間O(1)。

public class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;
        
        int maxProfit = 0;
        for (int i = 1; i < prices.length; i++) {
            int diff = prices[i] - prices[i - 1];
            if (diff > 0) {
                maxProfit += diff;
            }
        }
        
        return maxProfit;
    }
}

Best Time to Buy and Sell Stock III

題意: 用一個陣列表示股票每天的價格,陣列的第i個數表示股票在第i天的價格。最多交易兩次,手上最多隻能持有一支股票,求最大收益。

分析: 動態規劃法。以第i天為分界線,計算第i天之前進行一次交易的最大收益preProfit[i],和第i天之後進行一次交易的最大收益postProfit[i],最後遍歷一遍,max{preProfit[i] + postProfit[i]} (0≤i≤n-1)就是最大收益。第i天之前和第i天之後進行一次的最大收益求法同Best Time to Buy and Sell Stock I。

程式碼: 時間O(n),空間O(n)。

public class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;
        
        int n = prices.length;
        int[] preProfit = new int[n];
        int[] postProfit = new int[n];
        
        int curMin = prices[0];
        for (int i = 1; i < n; i++) {
            curMin = Math.min(curMin, prices[i]);
            preProfit[i] = Math.max(preProfit[i - 1], prices[i] - curMin);
        }
        
        int curMax = prices[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            curMax = Math.max(curMax, prices[i]);
            postProfit[i] = Math.max(postProfit[i + 1], curMax - prices[i]);
        }
        
        int maxProfit = 0;
        for (int i = 0; i < n; i++) {
            maxProfit = Math.max(maxProfit, preProfit[i] + postProfit[i]);
        }
        
        return  maxProfit;
    }
}

Best Time to Buy and Sell Stock IV

題意: 用一個陣列表示股票每天的價格,陣列的第i個數表示股票在第i天的價格。最多交易k次,手上最多隻能持有一支股票,求最大收益。

分析: 特殊動態規劃法。傳統的動態規劃我們會這樣想,到第i天時進行j次交易的最大收益,要麼等於到第i-1天時進行j次交易的最大收益(第i天價格低於第i-1天的價格),要麼等於到第i-1天時進行j-1次交易,然後第i天進行一次交易(第i天價格高於第i-1天價格時)。於是得到動規方程如下(其中diff = prices[i] – prices[i – 1]):

profit[i][j] = max(profit[i – 1][j], profit[i – 1][j – 1] + diff)

看起來很有道理,但其實不對,為什麼不對呢?因為diff是第i天和第i-1天的差額收益,如果第i-1天當天本身也有交易呢,那麼這兩次交易就可以合為一次交易,這樣profit[i – 1][j – 1] + diff實際上只進行了j-1次交易,而不是最多可以的j次,這樣得到的最大收益就小了。

那麼怎樣計算第i天進行交易的情況的最大收益,才會避免少計算一次交易呢?我們用一個區域性最優解和全域性最有解表示到第i天進行j次的收益,這就是該動態規劃的特殊之處。

用local[i][j]表示到達第i天時,最多進行j次交易的區域性最優解;用global[i][j]表示到達第i天時,最多進行j次的全域性最優解。它們二者的關係如下(其中diff = prices[i] – prices[i – 1]):

local[i][j] = max(global[i – 1][j – 1] + max(diff, 0), local[i – 1][j] + diff)
global[i][j] = max(global[i – 1][j], local[i][j])

其中的local[i – 1][j] + diff就是為了避免第i天交易和第i-1天交易合併成一次交易而少一次交易收益。 參考:http://www.cnblogs.com/grandyang/p/4295761.html

程式碼: 時間O(n),空間O(k)。

public class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices.length < 2) return 0;
        
        int days = prices.length;
        if (k >= days) return maxProfit2(prices);
        
        int[][] local = new int[days][k + 1];
        int[][] global = new int[days][k + 1];
        
        for (int i = 1; i < days ; i++) {
            int diff = prices[i] - prices[i - 1];
            
            for (int j = 1; j <= k; j++) {
                local[i][j] = Math.max(global[i - 1][j - 1], local[i - 1][j] + diff);
                global[i][j] = Math.max(global[i - 1][j], local[i][j]);
             }
        }
        
        return global[days - 1][k];
    }
    
    
    public int maxProfit2(int[] prices) {
        int maxProfit = 0;
        
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1]) {
                maxProfit += prices[i] - prices[i - 1];
            }
        }
        
        return maxProfit;
    }
}

我們知道,動規所用的二維輔助陣列可以降為一維的,即只用大小為k的一維陣列記錄到達第i天時的區域性最優解和全域性最優解。需要注意的是,由於第i天時交易k次的最優解依賴於第i-1天時交易k-1次的最優解,所以陣列更新應當從後往前(即從k到1)更新。

程式碼: 時間O(nk),空間O(k)。

public class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices.length < 2) return 0;
        if (k >= prices.length) return maxProfit2(prices);
        
        int[] local = new int[k + 1];
        int[] global = new int[k + 1];
        
        for (int i = 1; i < prices.length ; i++) {
            int diff = prices[i] - prices[i - 1];
            
            for (int j = k; j > 0; j--) {
                local[j] = Math.max(global[j - 1], local[j] + diff);
                global[j] = Math.max(global[j], local[j]);
            }
        }
        
        return global[k];
    }
    
    
    public int maxProfit2(int[] prices) {
        int maxProfit = 0;
        
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1]) {
                maxProfit += prices[i] - prices[i - 1];
            }
        }
        
        return maxProfit;
    }
}

補充: 這道題還有一個陷阱,就是當k大於天數時,其實就退化成 Best Time to Buy and Sell Stock II 了。就不能用動規來做了,為什麼?(請思考) 另外,Best Time to Buy and Sell Stock III 就是本題k=2的情況,所以說IV是II和III的綜合。

(完)

相關文章