[leetcode] BestTimeSellStock I, II, III, IV

BestTimeSellStock I

  • 問題描述:給定一組數字,代表每天股票的價格。假定現在只能進行一筆交易,計算出所能獲得利潤的最大值。
  • 解法:針對每個價格,我們只要知道它前面價格的最小值即好。所有我們可以遍歷整個數組,並用一個數字代表之前的所有數字的最小值。所以針對數組裏面的每個數,我們都能獲得一個如果在該點賣出的最大利潤值。我們計算這些利潤值裏面的最大值即可。
  • 時間複雜度:O(N), 空間複雜度:O(1)
  • 代碼:
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.empty())
            return 0;
        int low_price = prices[0];
        int profit = 0;
        for(int i=1;i<prices.size();i++){
            profit = max(profit, prices[i] - low_price);
            if(low_price > prices[i])
                low_price = prices[i];
        }
        return profit;
    }
};

BestTimeSellStock II

  • 問題描述:不同於I,你現在可以進行多筆交易。不過只能是buy-sell-buy-sell這樣交替進行,不支持buy-buy的操作。
  • 分析:因爲我們可以支持多筆交易,所以針對每個價格i,如果前一個價格低於該價格,我們就可以買入&賣出。這樣就可以賺差價啦。
    • 我們可能會想如果後面有更高的價格呢?比如說0, 5, 100. 我們在5的時候賺了5,但是在100的時候,我們可以賺95,所有和還是100。相當於我們在0買入,在100賣出。
  • 時間複雜度:O(N), 空間複雜度:O(1)
  • 代碼
int maxProfitV3(vector<int>& prices){
        int res = 0;
        int size = (int) prices.size();
        for(int i=1;i<size;i++){
            if(prices[i]>prices[i-1]){
                res += (prices[i]-prices[i-1]);
            }
        }
        return res;
    }

BestTimeSellStock III

  • 問題描述:不同於I和II,在本題中,我們最多隻能完成兩筆交易。不過只能是buy-sell-buy-sell這樣交替進行,不支持buy-buy的操作。
  • 分析:
    • 我們先回顧一下只能進行一筆交易的情況
      • 我們用dp[i]表示在區間[0, i]賣出進行一次交易所能獲得的最大利潤。
      • dp[i] = max(dp[i-1], prices[i] - min(prices[j])) j[0,i1]j\in[0, i-1]
    • 那現在我們可以進行兩筆交易
      • 假設我們已經得到了第一筆交易的dp[0~n]
      • 那麼我們如何計算第二筆交易的呢?
      • 同樣我們新建一個dp2[i],表示第2次在區間[0, i]賣出進行兩次所能獲得的最大利潤。
      • dp2[i] = max(dp2[i-1], prices[i] - prices[j] + dp1[j] for j in [0, i-1])
      • 上面這個式子是個O(N^2)的複雜度,如果我們直接實現這個DP,會TLE,那麼我怎麼優化呢?
      • dp2[i] = max(dp2[i-1], prices[i] + max(dp1[j] - prices[j]) for j in [0, i-1])
      • 我們知道我們i是從0開始遍歷的,所以我們可以維護一個變量tmpMax,代表就是當前的max(dp1[j] - prices[j]) for j in [0, i-1]。在每個price計算完成後,我們再更新tmpMax = max(tmpMax, dp1[i-1] - prices[i-1]);
    • 上面我們已經完成了最多兩筆交易的情況,那麼如果題目擴展一下,最多有K筆交易呢?這也就是題目BestTimeSellStock IV
      • 通過上面的分析,我們已經知道最多進行兩次交易如何計算最大值了,那麼怎麼擴展到K次呢?
      • 我們假設dp[k][i]表示第k次,在[0~i]內交易所能獲得的最大值。
      • dp[k][i] = max{dp[k][i-1], prices[i] - prices[j] + dp[k-1][j] for j in range(0, i-1)}
  • 代碼:
int maxProfitV3(vector<int>& prices){
        int res = 0;
        int size = (int) prices.size();
        if(size == 0)
            return 0;
        int K = 2;
        int dp[K + 1][size + 1];
        memset(dp, 0, sizeof(dp));
        // dp[k][i] = max(dp[k][i-1], prices[i] - prices[j] + dp[k-1][j] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + dp[k-1][j] - prices[j] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + max(dp[k-1][j] - prices[j]))
        for(int k=1;k<=K;k++){
            int tmpMax = dp[k-1][0] - prices[0];
            for(int i=2;i<=size;i++){
                dp[k][i] = max(dp[k][i-1], prices[i-1] + tmpMax);
                tmpMax = max(tmpMax, dp[k-1][i-1] - prices[i-1]);
                res = max(dp[k][i], res);
            }
        }
        return res;
    }
  • 分析:上面代碼的時間複雜度是O(KN), 空間複雜度是O(KN),那麼針對空間複雜度我們可以進一步優化。
    • 在進行第k輪計算時,當我們計算第dp[k][i]的時候,我們只會用到前一個的dp[k-1][i-1]。
    • 所以我們可以新建一個變量tmp,每次保存的就是前一輪的dp[k-1][i-1]。
    • 每次更新tmp,tmp1=dp[i], update dp[i], tmp = tmp1
    • 代碼:
int maxProfitV4(vector<int>& prices){
        int res = 0;
        int size = (int) prices.size();
        if(size == 0)
            return 0;
        int K = 2;
        int dp[size + 1];
        memset(dp, 0, sizeof(dp));
        // dp[k][i] = max(dp[k][i-1], prices[i] - prices[j] + dp[k-1][j-1] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + dp[k-1][j-1] - prices[j] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + max(dp[k-1][j-1] - prices[j]))
        for(int k=1;k<=K;k++){
            int tmpMax = dp[0] - prices[0];
            int saved_last = dp[1];
            for(int i=2;i<=size;i++){
                int tmp = dp[i];
                dp[i] = max(dp[i-1], prices[i-1] + tmpMax);
                tmpMax = max(tmpMax, saved_last - prices[i-1]);
                saved_last = tmp;
                res = max(dp[i], res);
            }
        }
        return dp[size];
    }
  • 分析:時間複雜度是O(KN), 空間複雜度我們已經降低到了O(N)。如果將上述代碼提交到IV,還是會TLE,那麼我們分析一下TLE的原因發現當我們K很大的時候,會進行很多不必要的操作。因爲我們一共就N個數字,所以最多K也應該小於等於N/2。所以當我們發現K>=N/2的時候,就表明我們可以在所有的數字上進行操作,這就轉化成了II的問題,那就是O(N)的複雜度。
  • 代碼:
int maxProfitV4(int& k, vector<int>& prices){
        int len = prices.size();
        if (len<2) return 0;
        if (k>len/2){ // simple case
            int ans = 0;
            for (int i=1; i<len; ++i){
                ans += max(prices[i] - prices[i-1],0);
            }
            return ans;
        }
        int res = 0;
        int size = (int) prices.size();
        if(size == 0)
            return 0;
        int K = k;
        int dp[size + 1];
        memset(dp, 0, sizeof(dp));
        // dp[k][i] = max(dp[k][i-1], prices[i] - prices[j] + dp[k-1][j-1] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + dp[k-1][j-1] - prices[j] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + max(dp[k-1][j-1] - prices[j]))
        for(int k=1;k<=K;k++){
            int tmpMax = dp[0] - prices[0];
            int saved_last = dp[1];
            for(int i=2;i<=size;i++){
                int tmp = dp[i];
                dp[i] = max(dp[i-1], prices[i-1] + tmpMax);
                tmpMax = max(tmpMax, saved_last - prices[i-1]);
                saved_last = tmp;
                res = max(dp[i], res);
            }
        }
        return dp[size];
    }

BestTimeSellStock with Cooldown

  • 問題描述:和BestTimeSellStock II相似,沒有交易次數的限制,但是我們在sell後必須經過一天的cooldown,不能進行操作。
  • 分析:
    • 假設說沒有cooldown,那麼我們可以的得到dp[i] = max(dp[i-1], prices[i] - prices[j] + dp[j])。
    • 但是現在我們有了一次cooldown,說明我們完成一次交易後不能立馬進行下一次交易。
    • 爲了方便理解,我們更改一下上面所用的dp[i],表示在第i個節點買入所能獲得的最大利潤,那麼dp[i] = max(prices[j] - prices[i] + dp[j+2], dp[j]) for j in [i+1, n]
    • 所以我們可以得到如下的代碼:
int maxProfit(vector<int>& prices) {
        int size = (int) prices.size();
        if(size == 0)
            return 0;
        int profits[size]; // 代表從第i點買入所能獲得的收益
        memset(profits, 0, sizeof(profits));
        int res = 0;
        for(int i=size-1;i>=0;i--){
            int profit = 0;
            for(int j=i+1;j<size;j++){
                // 某時刻賣出
                if((j+2) < size)
                    profit = max(prices[j] - prices[i] + profits[j+2], max(profit, profits[j]));
                else
                    profit = max(prices[j] - prices[i], max(profit, profits[j]));

            }

            profits[i] = profit;
            res = max(res, profit);
        }
        return res;
    }
  • 分析:上述代碼的時間複雜度是O(N^2),空間複雜度是O(N)。如何進行優化呢?我們發現如果沒有cooldown的時候,我們可以一直交易。但是有了cooldown了,說明我們不能每時每刻都交易了,只有在有些時候可以。那麼我們可以引出三個狀態state_reset(S0), state_buy(S1), state_sell(S2). 關係如下所示.
    在這裏插入圖片描述
  • 那我們可以得到他們呢之間的關係:
    • S0[i] = max(S0[i-1], S2[i-1])
    • S1[i] = max(S0[i-1]-prices[i], S1[i-1]) // 由S0買入
    • S2[i] = S1[i-1] + prices[i]
  • 所以我們可以得到一個O(N)的代碼:
int maxProfitV2(vector<int>& prices){
        int size = (int) prices.size();

        if(size == 0)
            return 0;
        int state_reset[size];
        int state_buy[size];
        int state_sell[size];
        memset(state_reset, 0, sizeof(state_reset));
        memset(state_buy, 0, sizeof(state_buy));
        memset(state_sell, 0, sizeof(state_sell));
        state_reset[0] = 0;
        state_buy[0] = -prices[0];
        state_sell[0] = -0x7FFFFFFF;
        for(int i=1;i<size;i++){
            state_reset[i] = max(state_reset[i-1], state_sell[i-1]); // 維持上一個狀態,即不買入,或者是上一個賣出的狀態的最大值
            state_buy[i] = max(state_reset[i-1] - prices[i], state_buy[i-1]);   // reset狀態過來,或者說不買入也不賣出
            state_sell[i] = state_buy[i-1] + prices[i]; // 賣出
        }
        return max(state_sell[size-1], max(state_reset[size-1], state_buy[size-1]));
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章