LeetCode(188) Best Time to Buy and Sell Stock IV (Java)

feliciafay發表於2015-04-19
題目如下:
Best Time to Buy and Sell Stock Total Accepted: 43912 Total Submissions: 135635 My Submissions Question Solution 
Say you have an array for which the ith element is the price of a given stock on day i.
If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.


分析如下:

感覺是一道非常困難的題目,不會做,全面看網上的討論。

題目的關鍵是要寫出下面的動態轉移方程:

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

global[i][j]=max(local[i][j],global[i-1][j]),

而這個方程的具體理解是這樣的。

首先global比較簡單,不過是不斷地和已經計算出的local進行比較,把大的儲存在global中。

然後看local,關鍵是要理解local的定義,local[i][j]表示,前i天進行了j次交易,並且第i天進行了第j次交易的最大利潤,所以local[i][j]中必然有一次交易,也就是當近一次交易,發生在第i天。 local由兩個部分的比較完成。

第一部分是,global[i-1][j-1]+max(diff,0), 表示的就是,前面把之前的j - 1次交易,放在之前的i - 1天,然後讓第i天來進行第j次交易,那麼加入此時diff(price[i] - price[i - 1])大於零,那麼正好可以可藉助這次交易的機會增長裡利潤(利潤= diff),否則的話,如果diff小於零,那就在第i天當天進行一次買賣,湊一次交易的次數,但是產生利潤為0.

第二部分是, local[i-1][j]+diff, 這裡的 local[i-1][j]表示的是,前面j次交易在第i -1天就已經完成了,可是因為說了local[a][b]一定要表達在第a天完成了b次交易的最大利潤,所以就需要強制使得交易在第i天發生,為了實現這一點,只需要在local[i - 1][j]的基礎上,加上diff ( = price[i] - price[i - 1])就可以了。如果diff < 0 那也沒有辦法,因為必須滿足local的定義。接下來算global的時候,總會保證取得一個更大的值。


關於正道題目的全文的分析,Code Ganker的還挺不錯。 下面全文摘抄作者的分析:

"這道題是Best Time to Buy and Sell Stock的擴充套件,現在我們最多可以進行兩次交易。我們仍然使用動態規劃來完成,事實上可以解決非常通用的情況,也就是最多進行k次交易的情況。


這裡我們先解釋最多可以進行k次交易的演算法,然後最多進行兩次我們只需要把k取成2即可。我們還是使用“區域性最優和全域性最優解法”。我們維護兩種量,一個是當前到達第i天可以最多進行j次交易,最好的利潤是多少(global[i][j]),另一個是當前到達第i天,最多可進行j次交易,並且最後一次交易在當天賣出的最好的利潤是多少(local[i][j])。下面我們來看遞推式。

全域性的比較簡單,
global[i][j]=max(local[i][j],global[i-1][j]),
也就是去當前區域性最好的,和過往全域性最好的中大的那個(因為最後一次交易如果包含當前天一定在區域性最好的裡面,否則一定在過往全域性最優的裡面)。

對於區域性變數的維護,遞推式是
local[i][j]=max(global[i-1][j-1]+max(diff,0),local[i-1][j]+diff),
也就是看兩個量,第一個是全域性到i-1天進行j-1次交易,然後加上今天的交易,如果今天是賺錢的話(也就是前面只要j-1次交易,最後一次交易取當前天),第二個量則是取local第i-1天j次交易,然後加上今天的差值(這裡因為local[i-1][j]比如包含第i-1天賣出的交易,所以現在變成第i天賣出,並不會增加交易次數,而且這裡無論diff是不是大於0都一定要加上,因為否則就不滿足local[i][j]必須在最後一天賣出的條件了)。

上面的演算法中對於天數需要一次掃描,而每次要對交易次數進行遞推式求解,所以時間複雜度是O(n*k),如果是最多進行兩次交易,那麼複雜度還是O(n)。空間上只需要維護當天資料皆可以,所以是O(k),當k=2,則是O(1)。"


我的程式碼:

/*
Input:	2, [6,1,3,2,4,7]
Output:	6
Expected:	7
*/
// 306ms
public class Solution {
    public int maxProfit(int k, int[] prices) {

        int n = prices.length; 
        // validate input 1
        if (k <= 0 || n == 0) return 0;
        
        // validate input 2 : if k is large enough, the question will be the same as question II.
        if (k >= n / 2) {
            int result = 0;
            for (int i = 1; i < n; ++i) {
                if (prices[i] - prices[i - 1] > 0) {
                    result += prices[i] - prices[i - 1];
                }
            }
            return result;
        }
        int[][] localProfit = new int[n][k + 1];
        int[][] globalProfit = new int[n][k + 1];

        //實際上沒有必要進行初始化 k = 1時候的情況,下面的nested for loop已經能夠處理這個初始化了。
        // int minPrice = prices[0];
        // for (int i = 1; i < n; ++i) {
        //     localProfit[i][1]= prices[i] - minPrice;
        //     globalProfit[i][1]= Math.max(localProfit[i][1], globalProfit[i - 1][1]);
        //     if (prices[i] < minPrice) {
        //         minPrice = prices[i];
        //     }
        // }
        
        for (int j = 1; j <= k; ++j) {
            for (int i = 1; i < n; ++i) {
                localProfit[i][j]= Math.max(
                    localProfit[i - 1][j] + prices[i] - prices[i -1], 
                    globalProfit[i - 1][j - 1] + Math.max(0, prices[i] - prices[i - 1]));
                globalProfit[i][j] = Math.max(localProfit[i][j], globalProfit[i - 1][j]);
            }
        }
        return globalProfit[n - 1][k];
    }
}


參考資料:

1 Code Ganker http://blog.csdn.net/linhuanmars/article/details/23236995

相關文章