LeetCode算法練習——動態規劃提高(五)

LeetCode309. 最佳買賣股票時機含冷凍期

給定一個整數數組,其中第 i 個元素代表了第 i 天的股票價格 。​設計一個算法計算出最大利潤。在滿足以下約束條件下,你可以儘可能地完成更多的交易(多次買賣一支股票):

  •     你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
  •     賣出股票後,你無法在第二天買入股票 (即冷凍期爲 1 天)。
示例:

輸入: [1,2,3,0,2]
輸出: 3 
解釋: 對應的交易狀態爲: [買入, 賣出, 冷凍期, 買入, 賣出]

定義狀態:我們設置一個數組dp[len][2],第二個維度爲1時表示此時持有股票,爲0表示未持有股票

狀態方程:

  • dp[i][0] = max(prices[i] - dp[i - 1][1], dp[i - 1][0]); // 第i天不持有股票,此時我們來記錄先前拋售利潤的最大值
    • case1:前一天買入股票,我們當天拋售;
    • case2:前一天未買入股票。
  • dp[i][1] = min(prices[i] - dp[i - 2][0], dp[i - 1][1]); // 第i天,持有股票,這時應讓持有的股票值最小,這樣相減時利潤最大
    • case1:冷卻期結束也未買入(即前兩天爲冷卻期,前一天也未買入),我們當天買入;
    • case2:前一天買入股票。

邊界情況:數組長度小於等於2的情況

初值定義:

  • dp[0][0] = 0;                                       // 第一天,不持有股票
  • dp[0][1] = prices[0];                               // 第一天,持有股票
  • dp[1][0] = max(dp[0][0], prices[1] - dp[0][1]);     // 第二天,不持有股票
  • dp[1][1] = min(prices[1] - dp[0][0], dp[0][1]);     // 第二天,持有股票
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0; // 特殊情況處理

        if(len == 2 && prices[0] >= prices[1]) return 0;    // 特殊情況處理
        else if(len == 2 && prices[0] < prices[1]) 
            return prices[1] - prices[0];                   // 特殊情況處理
        vector<vector<int>> dp(len, vector<int>(2, 0));
        dp[0][0] = 0;                                       // 第一天,不持有股票
        dp[0][1] = prices[0];                               // 第一天,持有股票
        dp[1][0] = max(dp[0][0], prices[1] - dp[0][1]);     // 第二天,不持有股票
        dp[1][1] = min(prices[1] - dp[0][0], dp[0][1]);     // 第二天,持有股票
        for(int i = 2; i < len; i++){
            dp[i][0] = max(prices[i] - dp[i - 1][1], dp[i - 1][0]); 
            // 第i天,不持有股票,可能此時已拋售這時應讓利潤最大
            dp[i][1] = min(prices[i] - dp[i - 2][0], dp[i - 1][1]); 
            // 第i天,持有股票,這時應讓持有的股票值最小
        }
        return dp[len - 1][0];
    }
};

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

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 兩筆 交易。

注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [3,3,5,0,0,3,1,4]
輸出: 6
解釋: 在第 4 天(股票價格 = 0)的時候買入,在第 6 天(股票價格 = 3)的時候賣出,這筆交易所能獲得利潤 = 3-0 = 3 。
     隨後,在第 7 天(股票價格 = 1)的時候買入,在第 8 天 (股票價格 = 4)的時候賣出,這筆交易所能獲得利潤 = 4-1 = 3 。

狀態表示

與上一題類似用 dp(i, j) 表示第 i 天進行 j 次交易的最大利潤。

狀態屬性

可以看到題目要求是最大利潤,所以屬性就是求一個最大值。

狀態計算

這個是最難的一步,方法就是從 dp(i,j) 表示的 實際意義 出發,儘可能地劃分出所有的條件。比如本題,我們從關鍵詞第 i 天,第 j 次交易進行思考,不難發現第 i 天可以劃分兩種情況:

  •     不交易:第 i 天什麼都沒發生,交易次數 j 不變,利潤爲前一天的利潤:dp(i, j)=dp(i − 1, j);
  •     交易:利潤爲前 j−1 次交易的利潤與第 i 天交易的利潤之和:dp(i, j)=dp(k − 1, j − 1) + prices(i) − prices(k);

由於買入賣出爲一次交易,因此準確來講如果在第 i 天進行交易就是指在第 i 天進行賣出操作,那麼是何時買入的呢?如果買入爲第 k 天,則 k 滿足 k∈[1,...,i]。
算法

由於重複計算 k 會多嵌套一層循環導致代碼超時,因此需要將第一筆交易獲得的利潤整合到第二筆交易的成本中。

dp(i, j)=prices(i) − (prices(k) − dp(k − 1, j − 1));前者表示第 i 天賣出價格,後者表示第 k 天買入價格減去第1次交易利潤

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0;                              // 特殊情況處理
        if(len == 2 && prices[0] >= prices[1]) return 0;    // 特殊情況處理
        else if(len == 2 && prices[0] < prices[1]) 
            return prices[1] - prices[0];                   // 特殊情況處理
        vector<vector<int>> dp(len, vector<int>(3, 0));
        for(int j = 1; j <= 2 ; j++){
            int minValue = prices[0];
            for (int i = 1; i < len; i++){
                minValue = min(minValue , prices[i] - dp[i - 1][j - 1]);
                dp[i][j] = max(dp[i - 1][j], prices[i] - minValue);
            }
        }
        return dp[len - 1][2];
    }
};

LeetCode188. 買賣股票的最佳時機 IV

此題與上一題的不同點在於可以進行k次交易,但解題思路是一樣的,只不過當k >= len/2時,可退化爲無限次交易的問題。

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0;                      // 特殊情況處理
        if (k >= len/2){                            //當k >= len/2時,可退化爲無限次交易的問題
            int res = 0;
            for (int i = 1; i < len; i ++){
                res += max(0, prices[i] - prices[i - 1]);
            }
            return res;
        }
        vector<vector<int>> dp(len, vector<int>(k + 1, 0));
        for(int j = 1; j <= k ; j++){
            int minValue = prices[0];
            for (int i = 1; i < len; i++){
                minValue = min(minValue , prices[i] - dp[i][j - 1]);
                dp[i][j] = max(dp[i - 1][j], prices[i] - minValue);
            }
        }
        return dp[len - 1][k];
    }
};

LeetCode312. 戳氣球

有 n 個氣球,編號爲0 到 n-1,每個氣球上都標有一個數字,這些數字存在數組 nums 中。現在要求你戳破所有的氣球。每當你戳破一個氣球 i 時,你可以獲得 nums[left] * nums[i] * nums[right] 個硬幣。 這裏的 left 和 right 代表和 i 相鄰的兩個氣球的序號。注意當你戳破了氣球 i 後,氣球 left 和氣球 right 就變成了相鄰的氣球。求所能獲得硬幣的最大數量。

說明:

    你可以假設 nums[-1] = nums[n] = 1,但注意它們不是真實存在的所以並不能被戳破。
    0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

示例:

輸入: [3,1,5,8]
輸出: 167 
解釋: nums = [3,1,5,8] --> [3,5,8] -->   [3,8]   -->  [8]  --> []
     coins =  3*1*5      +  3*5*8    +  1*3*8      + 1*8*1   = 167

狀態表示: f[i][j] 表示戳破 (i, j) 之間所有氣球的集合,屬性(最大值)這題的狀態表示很巧妙,用到了開區間

狀態計算:設 i 和 j 之間最後一個被戳破的氣球爲 k,那此時 (i, k) 和 (k, j) 之間的氣球已被戳破,最後 (i, j) 之間只剩下氣球 k ,相鄰的就是氣球 i 和 j,當j − i >= 2時,f[i][j] = f[i][k] + f[k][j] + p[i]∗p[k]∗p[j]

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        int length = nums.size();
        vector<int> tmp(length + 2, 1);     
        vector<vector<int>> dp(length + 2, vector<int>(length + 2, 0));
        for (int i = 1; i <= length; i++) 
            tmp[i] = nums[i - 1];           //[3,1,5,8]變成[1,3,1,5,8,1]
        for (int len = 1; len <= length + 2; len ++) {          //len的範圍[3,1,5,8,1]
            for (int i = 0; i + len - 1 <= length + 1; i++) {   //i爲左邊界範圍[1,3,1,5,8]
                int j = i + len - 1;                            //j爲右邊界
                if (len == 1 || len == 2) dp[i][j] = 0;         //意味着右邊界至少比左邊界大2
                for (int k = i + 1; k <= j - 1; k++) {          
                    //k爲最後戳破的氣球,此時 (i,k) 和 (k,j)之間的氣球已被戳破
                    //最後 (i,j) 之間只剩下氣球 k 
                    dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + tmp[i] * tmp[k] * tmp[j]);
                }
            }
        }
        return dp[0][length + 1];
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章