「leetcode」714. 買賣股票的最佳時機含手續費 超詳細講解

程式碼隨想錄發表於2020-12-29

本文 https://github.com/youngyangyang04/leetcode-master 已經收錄,裡面還有leetcode刷題攻略、各個型別經典題目刷題順序、思維導圖,可以fork到自己倉庫,有空看一看一定會有所收穫,如果對你有幫助也給一個star支援一下吧!

714. 買賣股票的最佳時機含手續費

題目連結:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/

給定一個整數陣列 prices,其中第 i 個元素代表了第 i 天的股票價格 ;非負整數 fee 代表了交易股票的手續費用。

你可以無限次地完成交易,但是你每筆交易都需要付手續費。如果你已經購買了一個股票,在賣出它之前你就不能再繼續購買股票了。

返回獲得利潤的最大值。

注意:這裡的一筆交易指買入持有並賣出股票的整個過程,每筆交易你只需要為支付一次手續費。

示例 1:
輸入: prices = [1, 3, 2, 8, 4, 9], fee = 2
輸出: 8

解釋: 能夠達到的最大利潤:
在此處買入 prices[0] = 1
在此處賣出 prices[3] = 8
在此處買入 prices[4] = 4
在此處賣出 prices[5] = 9
總利潤: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

注意:

  • 0 < prices.length <= 50000.
  • 0 < prices[i] < 50000.
  • 0 <= fee < 50000.

思路

本題相對於貪心演算法:122.買賣股票的最佳時機II,多新增了一個條件就是手續費。

貪心演算法

貪心演算法:122.買賣股票的最佳時機II中使用貪心策略不用關心具體什麼時候買賣,只要收集每天的正利潤,最後穩穩的就是最大利潤了。

而本題有了手續費,就要關係什麼時候買賣了,因為計算所獲得利潤,需要考慮買賣利潤可能不足以手續費的情況。

如果使用貪心策略,就是最低值買,最高值(如果算上手續費還盈利)就賣。

此時無非就是要找到兩個點,買入日期,和賣出日期。

  • 買入日期:其實很好想,遇到更低點就記錄一下。
  • 賣出日期:這個就不好算了,但也沒有必要算出準確的賣出日期,只要當前價格大於(最低價格+手續費),就可以收穫利潤,至於準確的賣出日期,就是連續收穫利潤區間裡的最後一天(並不需要計算是具體哪一天)。

所以我們在做收穫利潤操作的時候其實有三種情況:

  • 情況一:收穫利潤的這一天並不是收穫利潤區間裡的最後一天(不是真正的賣出,相當於持有股票),所以後面要繼續收穫利潤。
  • 情況二:前一天是收穫利潤區間裡的最後一天(相當於真正的賣出了),今天要重新記錄最小价格了。
  • 情況三:不作操作,保持原有狀態(買入,賣出,不買不賣)

貪心演算法C++程式碼如下:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int result = 0;
        int minPrice = prices[0]; // 記錄最低價格
        for (int i = 1; i < prices.size(); i++) {
            // 情況二:相當於買入
            if (prices[i] < minPrice) minPrice = prices[i]; 

            // 情況三:保持原有狀態(因為此時買則不便宜,賣則虧本)
            if (prices[i] >= minPrice && prices[i] <= minPrice + fee) {
                continue;
            }

            // 計算利潤,可能有多次計算利潤,最後一次計算利潤才是真正意義的賣出
            if (prices[i] > minPrice + fee) {
                result += prices[i] - minPrice - fee; 
                minPrice = prices[i] - fee; // 情況一,這一步很關鍵
            }
        }
        return result;
    }
};
  • 時間複雜度:O(n)
  • 空間複雜度:O(1)

從程式碼中可以看出對情況一的操作,因為如果還在收穫利潤的區間裡,表示並不是真正的賣出,而計算利潤每次都要減去手續費,所以要讓minPrice = prices[i] - fee;,這樣在明天收穫利潤的時候,才不會多減一次手續費!

大家也可以發現,情況三,那塊程式碼是可以刪掉的,我是為了讓程式碼表達清晰,所以沒有精簡。

動態規劃

我在公眾號「程式碼隨想錄」裡將在下一個系列詳細講解動態規劃,所以本題解先給出我的C++程式碼(帶詳細註釋),感興趣的同學可以自己先學習一下。

相對於貪心演算法:122.買賣股票的最佳時機II的動態規劃解法中,只需要在計算賣出操作的時候減去手續費就可以了,程式碼幾乎是一樣的。

C++程式碼如下:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        // dp[i][1]第i天持有的最多現金
        // dp[i][0]第i天持有股票所剩的最多現金
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(2, 0));
        dp[0][0] -= prices[0]; // 持股票
        for (int i = 1; i < n; i++) {
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
        }
        return max(dp[n - 1][0], dp[n - 1][1]);
    }
};
  • 時間複雜度:O(n)
  • 空間複雜度:O(n)

當然可以對空間經行優化,因為當前狀態只是依賴前一個狀態。

C++ 程式碼如下:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        int holdStock = (-1) * prices[0]; // 持股票
        int saleStock = 0; // 賣出股票
        for (int i = 1; i < n; i++) {
            int previousHoldStock = holdStock;
            holdStock = max(holdStock, saleStock - prices[i]);
            saleStock = max(saleStock, previousHoldStock + prices[i] - fee);
        }
        return saleStock;
    }
};
  • 時間複雜度:O(n)
  • 空間複雜度:O(1)

總結

本題貪心的思路其實是比較難的,動態規劃才是常規做法,但也算是給大家擴充一下思路,感受一下貪心的魅力。

後期我們在講解 股票問題系列的時候,會用動規的方式把股票問題穿個線。

我是程式設計師Carl,可以找我組隊刷題,也可以在B站上找到我,本文leetcode刷題攻略已收錄,更多精彩演算法文章盡在公眾號:程式碼隨想錄,關注後就會發現和「程式碼隨想錄」相見恨晚!

程式碼隨想錄

如果感覺對你有幫助,不要吝嗇給一個?吧!

相關文章