@laigary.com~/interview/coding/714-best-time-to-buy….md$
$ cat ./coding/714-best-time-to-buy-and-sell-stock-with-transaction-fee.md
[Coding]·2023-01-29·7 min read

714. Best Time to Buy and Sell Stock with Transaction Fee

714. Best Time to Buy and Sell Stock with Transaction Fee

交易次數無限,但每完成一筆交易要付 fee 的手續費

思路

這一題改的旋鈕是「每筆交易扣手續費」,交易次數仍然無限。 完整的狀態機和其他五題的對照見 股票買賣家族模板

122 出發,唯一要改的是在轉移式裡把 fee 減掉

buys[i] = max(buys[i-1], sells[i-1] - prices[i] - fee)   # 買進時就先付掉手續費

在買進時扣還是賣出時扣? 兩種都對,只要整份程式碼只扣一次就好。

  • 買進時扣(這裡的寫法):一買進就把手續費認列成成本
  • 賣出時扣sells[i] = max(sells[i-1], buys[i-1] + prices[i] - fee)

差別只在中間狀態的數值,最終答案相同。買進時扣有個小好處:sells 的語意保持乾淨(就是「手上沒股票時的獲利」),不用記得它有沒有被扣過。

手續費改變了什麼

122 的貪心解(「把所有上漲區段吃下來」)在這題不成立。因為每拆一筆交易就多付一次手續費,a → b → c 拆成兩筆要付兩次費,可能還不如一次做完。

所以手續費的作用是懲罰頻繁交易,讓「要不要現在賣掉」變成一個真的要權衡的決定 —— 這正是狀態機存在的意義。這也是為什麼加了任何限制(冷凍期、手續費、次數上限)就必須回到 DP。

不過這題還是有貪心解,只是比 122 複雜:記住「有效買入價」,只在 price > buy + fee 時才賣,並在賣出後把買入價設成 price - fee 以允許連續上漲繼續累積。想得出來很漂亮,但推導比 DP 難,面試時給 DP 版比較穩。

解題方向

class Solution:
    def maxProfit(self, prices: List[int], fee: int) -> int:
        if len(prices) < 2:
            return 0

        buys = [float('-inf')] * len(prices) 
        sells = [0] * len(prices) 

        for i in range(len(prices)):
            if i == 0:
                buys[i] = max(buys[i], 0 - prices[i] - fee)
            else:
                buys[i] = max(buys[i-1], sells[i-1] - prices[i] - fee)
            sells[i] = max(sells[i-1], buys[i-1] + prices[i])
        return sells[-1]

122 逐字比對,只差 - fee 那兩處。這就是把家族當成一個狀態機來記的好處:新題目來了只要問「規則改了哪一項」,然後改對應的那一行。

buys 初始化成 -inf 是哨兵(「還沒開始就持有股票」不是合法狀態),第 0 天的 -prices[0] - fee 則是真正的起點。

兩個陣列可以省成兩個變數,因為轉移只看前一天:

        buys, sells = float('-inf'), 0
        for price in prices:
            buys = max(buys, sells - price - fee)
            sells = max(sells, buys + price)
        return sells

和 122 一樣,sells 用到同一輪剛更新的 buys 是無害的 —— 那代表「今天買今天賣」,獲利是 -fee,只會更差,max 不會選它。(309 冷凍期 那題就不能這樣寫。)

補充

整個家族的對照股票買賣家族模板。這題和 122309 是同一組(k 無限),三題的程式碼幾乎一樣:

買進轉移
122max(buys, sells - price)
309max(buys, sells_前天 - price)
714 這題max(buys, sells - price - fee)

一起看最有效率 —— 三題其實是同一題的三種微調。

複雜度

  • 時間 O(n) — 一趟迴圈,每天常數次計算
  • 空間 O(n) 用陣列、O(1) 用滾動變數

其中 n 是天數。手續費不影響複雜度,只影響轉移式裡的一個常數項。