Best Time to Buy and Sell Stock III
Best Time to Buy and Sell Stock III: max profit using at most two non-overlapping buy-sell transactions.
- 1 <= prices.length <= 10⁵
- 0 <= prices[i] <= 10⁵
Intuition
Best time to buy and sell stock iii caps the number of transactions at two, sitting between the single-transaction original and the unlimited version. That cap is what makes it Hard — the greedy no longer applies, and a transaction count must be carried.
The direct approach tracks four states, each representing how far through the two transactions you are:
- First buy, first sell, second buy, second sell — a share is bought, sold, bought again, and sold again, in that order.
Each state is the best balance achievable at that stage. firstBuy is the balance after one purchase, so it is negative. firstSell is the balance after one completed transaction. secondBuy is the balance after buying again out of the first sale's proceeds. secondSell is the final answer.
What makes this correct is that each state builds on the one before it:
secondBuy = max(secondBuy, firstSell − price) funds the second purchase from the profit already banked. That chaining is what enforces the ordering — the second transaction cannot begin before the first has finished.
Updating all four in a single pass, in order, works even though later states read values updated earlier in the same iteration. Allowing a same-day sell and rebuy is harmless, since it produces a net-zero pair that never beats the alternative.
Initialise both buy states to negative infinity — or firstBuy to −prices[0] — so an unfilled state never looks profitable.
An alternative splits the array at every index, computing the best profit before and after each split point in two passes. Same O(n), and a useful cross-check.
Four states chained in sequence: buy1 → sell1 → buy2 → sell2. Each one feeds the next, and updating them in that order within a single loop means the second transaction's purchase already sees the first sale's profit — the chaining does the bookkeeping.
Approach
Before reading on: price up what the direct approach costs here, then ask what a single cell should mean, and which earlier cells it needs. Aim for O(n) time and O(1) space.
Recognise why the cap matters
At most two transactions means the greedy from the unlimited version fails, and a transaction count must be carried through the scan. That is the whole step up in difficulty.
Define four ordered states
First buy, first sell, second buy, second sell. Each is the best balance achievable at that stage, and their order encodes the required sequence of trades.
Update the first transaction
firstBuy = max(firstBuy, -price) and firstSell = max(firstSell, firstBuy + price). These mirror the single-transaction problem exactly.
Chain the second onto the first
secondBuy = max(secondBuy, firstSell - price) funds the second purchase from banked profit. This chaining is what enforces the ordering between the two transactions.
Complete the second sale
secondSell = max(secondSell, secondBuy + price) holds the final answer. Because a second transaction is optional, this never falls below firstSell.
Initialise the buy states low
Set both buy states to negative infinity, or firstBuy to -prices[0]. Starting at 0 makes an unfilled state look profitable and inflates the result.
Know the two-pass alternative
Split at every index, computing the best profit before and after each split. Same O(n) time, and a useful cross-check when the four-state version misbehaves.
Cost of the scan
One pass updating four variables gives O(n) time and O(1) space, with no table required despite the transaction limit.
Solution & live demo
Common pitfalls
Splitting the array at every index
for i in range(n):
best = max(best, maxOne(prices[:i]) + maxOne(prices[i:]))buy2 = max(buy2, sell1 - prices[i])
That's O(n²), or O(n) with two precomputed arrays. The four chained states carry the same information in four scalars and one pass.
Updating the states in reverse order
sell2 = max(sell2, buy2 + prices[i]) buy2 = max(buy2, sell1 - prices[i]) ...
buy1 = ...; sell1 = ...; buy2 = ...; sell2 = ...
Each state depends on the one before it on the same day — that's what allows buying and selling on the same index, which is a legal no-op. Reversing the order breaks the chain and undercounts.
Initialising buy2 to zero
buy2 = 0
buy2 = -prices[0]
buy2 holds profit-minus-cost after two purchases, which starts negative. Seeding at 0 pretends a share was acquired for free and inflates the answer.
Edge cases
buy1, buy2 chase the lowest price but sell1, sell2 never exceed 0, so the answer correctly stays 0 with no forced losing trade.
sell2 stays equal to sell1 throughout since a second trade never improves on it, correctly reducing to the single-transaction answer.
With 0 or 1 prices there is no valid transaction; the loop from day 1 onward simply does not run and profit stays 0.
buy2 only ever draws funds from sell1, which already reflects a closed first trade, so the two captured gains cannot overlap in time.