LeetCode #714 Medium

Best Time to Buy and Sell Stock with Transaction Fee

Max profit from unlimited trades where every completed transaction costs a flat fee.

dynamic-programmingarraygreedystate-machine
Open on LeetCode ↗
02

Intuition

The instinctive move is to subtract the fee whenever money changes hands, once on the buy and once on the sell. That double-charges every round trip and undercounts profit. The fee is per completed transaction, not per action, so charge it exactly once, conventionally when you sell, and leave the buy transition untouched. Once that's settled, this is otherwise the same two-state recurrence as unlimited-transaction stock trading: hold a share or be free of one, each a running max over what you did yesterday.

How to spot this pattern

Two states, holding and free, with the fee charged once per completed transaction. Subtracting it on the sale rather than the purchase keeps the arithmetic in one place and makes the final answer read directly off free.

03

Approach

1

Two rolling states

hold tracks the best profit while owning a share; free tracks the best profit owning nothing. Initialize hold to -prices[0] (buy immediately) and free to 0.

2

Charge the fee once, on the sell

Each day, new_hold is the better of keeping yesterday's share or buying today out of yesterday's free. new_free is the better of staying free or selling today's held share, subtracting the fee only on that sell transition: hold + price - fee.

3

Answer is the final free state

Ending the timeline while still holding a share can never be optimal, since selling it (even after paying the fee) is only ever better or equal. The answer is free after the last day.

04

Solution & live demo

1class Solution:
2 def maxProfit(self, prices: list[int], fee: int) -> int:
3 n = len(prices)
4 if n < 2:
5 return 0
6 hold, free = -prices[0], 0
7 for i in range(1, n):
8 new_hold = max(hold, free - prices[i])
9 new_free = max(free, hold + prices[i] - fee)
10 hold, free = new_hold, new_free
11 return free
05

Common pitfalls

Charging the fee twice

✗ Wrong
new_hold = max(hold, free - prices[i] - fee)
new_free = max(free, hold + prices[i] - fee)
✓ Right
new_hold = max(hold, free - prices[i])
new_free = max(free, hold + prices[i] - fee)

A transaction is a buy and a sell, and the fee applies to the pair. Deducting on both halves doubles the cost and suppresses trades that are actually profitable.

Updating the states sequentially

✗ Wrong
hold = max(hold, free - prices[i])
free = max(free, hold + prices[i] - fee)
✓ Right
new_hold = ...
new_free = ...
hold, free = new_hold, new_free

The free line would use today's hold, letting a share be bought and sold within the same day for a guaranteed profit that isn't real. Both must read yesterday's values.

Returning the holding state

✗ Wrong
return max(hold, free)
✓ Right
return free

hold represents cash tied up in an unsold share and is always worse than having sold. The optimal plan never ends mid-position, so free is the answer by construction.

06

Edge cases

Fee larger than any possible gain

Every candidate sell value (hold + price - fee) stays below just staying free, so free never updates above 0 and the answer is 0.

Single day of prices

hold becomes -prices[0] but free stays 0 since there is no second day to sell on; answer is 0.

Many small consecutive rises

The state machine naturally avoids paying the fee on every micro-uptick by only committing to a sell when hold + price - fee beats holding out for a later price.

Fee equal to zero

Reduces exactly to the unlimited-transactions problem, since the sell transition becomes hold + price with no penalty.

07

Complexity

Time
O(n)
Space
O(1)
Two rolling scalars, one pass over the prices.