Model the trading rules as a three-state machine — hold, sold, and rest — and track only the rolling maximum profit for each state. The cooldown constraint collapses naturally into one forbidden transition.
Given an array prices where prices[i] is the stock price on day i, find the maximum profit with unlimited transactions, subject to one rule: after you sell, you must skip exactly one day (the cooldown) before buying again. Concrete example: prices = [1, 2, 3, 0, 2] → best strategy is buy day 0 (price 1), sell day 2 (price 3, profit +2), cool down day 3, buy day 3 (price 0), sell day 4 (price 2, profit +2) → total profit = 3.
hold = -prices[0] (bought), sold = -∞ (impossible), rest = 0 (did nothing).prevHold, prevSold, prevRestso simultaneous updates don't clobber each other.hold = max(prevHold, prevRest - prices[i]) — keep holding, or buy today (can only buy from the rest state, enforcing cooldown).sold = prevHold + prices[i]— sell today, so profit jumps by today's price.rest = max(prevRest, prevSold)— stay idle, or absorb yesterday's sell (the cooldown day itself earns nothing).max(sold, rest, 0) — we never end holding; take the best of sold and rest.hold = max(hold, rest - p) then sold = hold + p in sequence, the second line reads the already-updated hold — a silent bug that inflates profits. Always capture prevHold / prevSold / prevRest first.A naïve DP would allocate a 3×n table, but since each state on day i only depends on day i-1, we can collapse to three scalars. Time is a single O(n) pass; space is O(1).
[1, 2, 3, 0, 2]. Track three states: hold (own stock), sold (just sold), rest (cooldown, can buy next day). Init: hold = -1, sold = -∞, rest = 0.1function maxProfit(prices: number[]): number {2 // Three states:3 // hold = max profit while currently holding stock4 // sold = max profit on the day we just sold (triggers cooldown)5 // rest = max profit while on cooldown / idle (can buy next day)6▶ let hold = -prices[0]; // bought on day 07▶ let sold = -Infinity; // impossible to have sold before day 08▶ let rest = 0; // started with nothing, did nothing910 for (let i = 1; i < prices.length; i++) {11 const prevHold = hold;12 const prevSold = sold;13 const prevRest = rest;1415 // keep holding OR buy from rest (rest -> hold, NOT sold -> hold, so cooldown enforced)16 hold = Math.max(prevHold, prevRest - prices[i]);17 // sell today (hold -> sold)18 sold = prevHold + prices[i];19 // stay idle OR recover from yesterday's sale (sold -> rest)20 rest = Math.max(prevRest, prevSold);21 }2223 // We never end while holding; answer is the better of sold and rest24 return Math.max(sold, rest, 0);25}
function maxProfit(prices: number[]): number {
// Three states:
// hold = max profit while currently holding stock
// sold = max profit on the day we just sold (triggers cooldown)
// rest = max profit while on cooldown / idle (can buy next day)
let hold = -prices[0]; // bought on day 0
let sold = -Infinity; // impossible to have sold before day 0
let rest = 0; // started with nothing, did nothing
for (let i = 1; i < prices.length; i++) {
const prevHold = hold;
const prevSold = sold;
const prevRest = rest;
// keep holding OR buy from rest (rest -> hold, NOT sold -> hold, so cooldown enforced)
hold = Math.max(prevHold, prevRest - prices[i]);
// sell today (hold -> sold)
sold = prevHold + prices[i];
// stay idle OR recover from yesterday's sale (sold -> rest)
rest = Math.max(prevRest, prevSold);
}
// We never end while holding; answer is the better of sold and rest
return Math.max(sold, rest, 0);
}hold = best profit while you own a share. sold = best profit on days you just executed a sale (tomorrow is forced cooldown). rest = best profit while idle or cooling down.hold = -prices[0] (paid that price). We cannot have sold yet so sold = -Infinity. We haven't done anything so rest = 0.prevHold, prevSold, prevRest before mutating is essential — forgetting this is the most common bug.Math.max(prevHold, prevRest - prices[i]): either we were already holding (no action), or we buy today. Crucially we can only buy from the rest state, not the sold state — that single restriction enforces the entire cooldown rule.prevHold + prices[i]: the only way to reach soldis by selling. We add today's price to the best profit from holding.Math.max(prevRest, prevSold): either we were already resting/idle, or we transition from yesterday's sold (the cooldown day itself: we earned nothing extra, but the cash from the sale is now available for future buys).Math.max(sold, rest, 0): the better of having just sold or being idle, with a floor of 0 for a declining market.rest scalar with a queue of length k; on each day shift the queue and the oldest sold becomes available.sold = prevHold + prices[i] - fee. The rest of the transitions are unchanged.hold[k], sold[k] — each sell increments the transaction counter. (LC 188.)function maxProfit(prices: number[]): number {
// Three states:
// hold = max profit while currently holding stock
// sold = max profit on the day we just sold (triggers cooldown)
// rest = max profit while on cooldown / idle (can buy next day)
let hold = -prices[0]; // bought on day 0
let sold = -Infinity; // impossible to have sold before day 0
let rest = 0; // started with nothing, did nothing
for (let i = 1; i < prices.length; i++) {
const prevHold = hold;
const prevSold = sold;
const prevRest = rest;
// keep holding OR buy from rest (rest -> hold, NOT sold -> hold, so cooldown enforced)
hold = Math.max(prevHold, prevRest - prices[i]);
// sell today (hold -> sold)
sold = prevHold + prices[i];
// stay idle OR recover from yesterday's sale (sold -> rest)
rest = Math.max(prevRest, prevSold);
}
// We never end while holding; answer is the better of sold and rest
return Math.max(sold, rest, 0);
}Walk day by day and branch on the only choices available: if not holding, either buy today or skip; if holding, either sell today (then jump two days ahead for the mandatory cooldown) or keep holding. Return the best profit over all branches.
function maxProfit(prices: number[]): number {
function dfs(day: number, holding: boolean): number {
if (day >= prices.length) return 0;
// Option 1: do nothing today
const skip = dfs(day + 1, holding);
let act: number;
if (holding) {
// sell today, then cooldown forces us to day + 2
act = prices[day] + dfs(day + 2, false);
} else {
// buy today
act = -prices[day] + dfs(day + 1, true);
}
return Math.max(skip, act);
}
return dfs(0, false);
}(day, holding), so memoising on that pair — or rolling the three states forward with O(1) variables — collapses it to O(n).| "sell with cooldown" / "skip one day after selling" | state machine: hold / sold / rest |
| unlimited transactions + constraint after selling | three rolling scalars + snapshot-before-update |
| stock problem with a cooldown or fee | extend 2-state hold/nothold to include the restricted transition |
| "at most k" or "at most 2" transactions | same state machine + transaction counter dimension |
let hold = -prices[0];
let sold = -Infinity;
let rest = 0;
for (let i = 1; i < prices.length; i++) {
const ph = hold, ps = sold, pr = rest;
hold = Math.max(ph, pr - prices[i]); // keep | buy from rest
sold = ph + prices[i]; // sell (was holding)
rest = Math.max(pr, ps); // idle | come off cooldown
}
return Math.max(sold, rest, 0);hold (own stock), sold (just sold — next day is cooldown), rest (idle / cooling down — free to buy tomorrow).prices = [1, 2, 3, 0, 2], what is the maximum profit?Math.max(sold, rest, 0) instead of Math.max(hold, sold, rest)?prices = [1] (single day), what does the algorithm return?