1. 程式人生 > >[LeetCode] Best Time to Buy and Sell Stock III 買股票的最佳時間之三

[LeetCode] 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).

這道是買股票的最佳時間系列問題中最難最複雜的一道,前面兩道

Best Time to Buy and Sell Stock 買賣股票的最佳時間Best Time to Buy and Sell Stock II 買股票的最佳時間之二的思路都非常的簡潔明瞭,演算法也很簡單。而這道是要求最多交易兩次,找到最大利潤,還是需要用動態規劃Dynamic Programming來解,而這裡我們需要兩個遞推公式來分別更新兩個變數local和global,參見網友Code Ganker的部落格,我們其實可以求至少k次交易的最大利潤,找到通解後可以設定 k = 2,即為本題的解答。我們定義local[i][j]為在到達第i天時最多可進行j次交易並且最後一次交易在最後一天賣出的最大利潤,此為區域性最優。然後我們定義global[i][j]為在到達第i天時最多可進行j次交易的最大利潤,此為全域性最優。它們的遞推式為:

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])

其中區域性最優值是比較前一天並少交易一次的全域性最優加上大於0的差值,和前一天的區域性最優加上差值中取較大值,而全域性最優比較區域性最優和前一天的全域性最優。程式碼如下:

解法一:

class Solution {
public:
    int maxProfit(vector<int> &prices) {
        
if (prices.empty()) return 0; int n = prices.size(), g[n][3] = {0}, l[n][3] = {0}; for (int i = 1; i < prices.size(); ++i) { int diff = prices[i] - prices[i - 1]; for (int j = 1; j <= 2; ++j) { l[i][j] = max(g[i - 1][j - 1] + max(diff, 0), l[i - 1][j] + diff); g[i][j] = max(l[i][j], g[i - 1][j]); } } return g[n - 1][2]; } };

下面這種解法用一維陣列來代替二維陣列,可以極大的節省了空間,由於覆蓋的順序關係,我們需要j從2到1,這樣可以取到正確的g[j-1]值,而非已經被覆蓋過的值,參見程式碼如下:

解法二:

class Solution {
public:
    int maxProfit(vector<int> &prices) {
        if (prices.empty()) return 0;
        int g[3] = {0};
        int l[3] = {0};
        for (int i = 0; i < prices.size() - 1; ++i) {
            int diff = prices[i + 1] - prices[i];
            for (int j = 2; j >= 1; --j) {
                l[j] = max(g[j - 1] + max(diff, 0), l[j] + diff);
                g[j] = max(l[j], g[j]);
            }
        }
        return g[2];
    }
};

我們如果假設prices陣列為1, 3, 2, 9, 那麼我們來看每次更新時local 和 global 的值:

第一天兩次交易:      第一天一次交易:

local:    0 0 0       local:    0 0 0 

global:  0 0 0       global:  0 0 0

第二天兩次交易:      第二天一次交易:

local:    0 0 2       local:    0 2 2 

global:  0 0 2       global:  0 2 2

第三天兩次交易:      第三天一次交易:

local:    0 2 2       local:    0 1 2 

global:  0 2 2       global:  0 2 2

第四天兩次交易:      第四天一次交易:

local:    0 1 9       local:    0 8 9 

global:  0 2 9       global:  0 8 9

在網友@的提醒下,發現了其實上述的遞推公式關於local[i][j]的可以稍稍化簡一下,我們之前定義的local[i][j]為在到達第i天時最多可進行j次交易並且最後一次交易在最後一天賣出的最大利潤,然後網友@解釋了一下第 i 天賣第 j 支股票的話,一定是下面的一種:

1. 今天剛買的
那麼 Local(i, j) = Global(i-1, j-1)
相當於啥都沒幹

2. 昨天買的
那麼 Local(i, j) = Global(i-1, j-1) + diff
等於Global(i-1, j-1) 中的交易,加上今天干的那一票

3. 更早之前買的
那麼 Local(i, j) = Local(i-1, j) + diff
昨天別賣了,留到今天賣

但其實第一種情況是不需要考慮的,因為當天買當天賣不會增加利潤,完全是重複操作,這種情況可以歸納在global[i-1][j-1]中,所以我們就不需要max(0, diff)了,那麼由於兩項都加上了diff,所以我們可以把diff抽到max的外面,所以更新後的遞推公式為:

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

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

類似題目: