1. 程式人生 > >Best Time to Buy and Sell Stock III 最佳時間買入賣出股票(最多兩次買賣)@LeetCode

Best Time to Buy and Sell Stock III 最佳時間買入賣出股票(最多兩次買賣)@LeetCode

轉載:https://blog.csdn.net/fightforyourdream/article/details/14503469

題目:

最佳時間買入賣出股票:你有一個數組儲存了股票在第i天的價錢,現在你最多進行兩次買賣,但同一時間你手上只能保持一個股票,如何賺的最多

思路:

知道要用DP做,但是一開始思路是錯的。後來參考了 http://blog.csdn.net/pickless/article/details/12034365

才意識到可以在整個區間的每一點切開,然後分別計算左子區間和右子區間的最大值,然後再用O(n)時間找到整個區間的最大值。

看來以後碰到與2相關的問題,一定要想想能不能用二分法來做!

下面複製pickless的講解,我覺得我不能比他講的更好了

O(n^2)的演算法很容易想到:

找尋一個點j,將原來的price[0..n-1]分割為price[0..j]和price[j..n-1],分別求兩段的最大profit。

進行優化:

對於點j+1,求price[0..j+1]的最大profit時,很多工作是重複的,在求price[0..j]的最大profit中已經做過了。

類似於Best Time to Buy and Sell Stock,可以在O(1)的時間從price[0..j]推出price[0..j+1]的最大profit。

但是如何從price[j..n-1]推出price[j+1..n-1]?反過來思考,我們可以用O(1)的時間由price[j+1..n-1]推出price[j..n-1]。

最終演算法:

陣列l[i]記錄了price[0..i]的最大profit,

陣列r[i]記錄了price[i..n]的最大profit。

已知l[i],求l[i+1]是簡單的,同樣已知r[i],求r[i-1]也很容易。

最後,我們再用O(n)的時間找出最大的l[i]+r[i],即為題目所求。


package Level4;
 
import java.util.Arrays;
 
/**
 * Best Time to Buy and Sell Stock III
 * 
 *  Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete at most two transactions.
Note:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
http://blog.csdn.net/pickless/article/details/12034365
 *
 */
public class S123 {
 
    public static void main(String[] args) {
//        int[] prices = {3,3,5,0,0,3,1,4};
        int[] prices = {2,1,2,0,1};
        System.out.println(maxProfit(prices));
    }
    
    // 基本思想是分成兩個時間段,然後對於某一天,計算之前的最大值和之後的最大值
    public static int maxProfit(int[] prices) {
        if(prices.length == 0){
            return 0;
        }
        
        int max = 0;
        // dp陣列儲存左邊和右邊的利潤最大值
        int[] left = new int[prices.length];        // 計算[0,i]區間的最大值
        int[] right = new int[prices.length];    // 計算[i,len-1]區間的最大值
        
        process(prices, left, right);
        
        // O(n)找到最大值
        for(int i=0; i<prices.length; i++){
            max = Math.max(max, left[i]+right[i]);
        }
        
        return max;
    }
    
    public static void process(int[] prices, int[] left, int[] right){
        left[0] = 0;
        int min = prices[0];        // 最低買入價
        
        // 左邊遞推公式
        for(int i=1; i<left.length; i++){
            left[i] = Math.max(left[i-1], prices[i]-min);    // i的最大利潤為(i-1的利潤)和(當前賣出價和之前買入價之差)的較大那個
            min = Math.min(min, prices[i]);        // 更新最小買入價
        }
        
        right[right.length-1] = 0;
        int max = prices[right.length-1];        // 最高賣出價
        // 右邊遞推公式
        for(int i=right.length-2; i>=0; i--){
            right[i] = Math.max(right[i+1], max-prices[i]);    // i的最大利潤為(i+1的利潤)和(最高賣出價和當前買入價之差)的較大那個
            max = Math.max(max, prices[i]);        // 更新最高賣出價
        }
        
//        System.out.println(Arrays.toString(left));
//        System.out.println(Arrays.toString(right));
    }
 
}

下面的解法主要是能把兩次的限制推廣到k次交易:

這道題是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]),
也就是去當前區域性最好的,和過往全域性最好的中大的那個(因為最後一次交易如果包含當前天一定在區域性最好的裡面,否則一定在過往全域性最優的裡面)。

全域性(到達第i天進行j次交易的最大收益) = max{區域性(在第i天交易後,恰好滿足j次交易),全域性(到達第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]必須在最後一天賣出的條件了)。

區域性(在第i天交易後,總共交易了j次) =  max{情況2,情況1}

情況1:在第i-1天時,恰好已經交易了j次(local[i-1][j]),那麼如果i-1天到i天再交易一次:即在第i-1天買入,第i天賣出(diff),則這不併不會增加交易次數!【例如我在第一天買入,第二天賣出;然後第二天又買入,第三天再賣出的行為  和   第一天買入,第三天賣出  的效果是一樣的,其實只進行了一次交易!因為有連續性】

情況2:第i-1天后,共交易了j-1次(global[i-1][j-1]),因此為了滿足“第i天過後共進行了j次交易,且第i天必須進行交易”的條件:我們可以選擇1:在第i-1天買入,然後再第i天賣出(diff),或者選擇在第i天買入,然後同樣在第i天賣出(收益為0)。

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


public class Solution {
    public int maxProfit(int[] prices) {
        return max(prices, 2);
    }
    
    public int max(int[] prices, int k) {       // k: k times transactions
        int len = prices.length;
        if(len == 0) {
            return 0;
        }
        int[][] local = new int[len][k+1];      // local[i][j]: max profit till i day, j transactions, where there is transaction happening on i day
        int[][] global = new int[len][k+1];     // global[i][j]: max profit across i days, j transactions
        for(int i=1; i<len; 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]+Math.max(diff,0), local[i-1][j]+diff);
                global[i][j] = Math.max(global[i-1][j], local[i][j]);
            }
        }
        return global[len-1][k];
    }
}