You can make as many transactions as you like, but after every sell you must wait one day before buying again. Without that cooldown, the greedy strategy of taking every price increase would be optimal. The cooldown breaks it: selling on day i locks you out of buying on day i+1, so a sell today can cost you a better opportunity tomorrow. At each point you have to weigh selling now and sitting out a day against holding and selling later.
That decision structure is a state machine. On any given day you are in one of a few states, and each state allows different moves. The cooldown adds a third state beyond the usual "holding" and "not holding".
1 <= prices.length <= 5000 → O(n^2) is about 25 million operations, which would pass but is unnecessary. Every approach below runs in O(n).0 <= prices[i] <= 1000 → Total profit is at most 5000 × 1000, so a 32-bit integer is enough. Prices can be zero, so buying at no cost is possible.On each day the decision depends on a single piece of state: whether you currently hold a stock. If you hold one, you can sell or keep holding. If you do not, you can buy or skip the day. Define a recursive function dfs(day, holding) that returns the maximum profit from day day onwards. A sell jumps to day + 2 instead of day + 1, which enforces the cooldown without tracking it as separate state.
The recursion has overlapping subproblems. dfs(3, false) can be reached from multiple paths: by selling on day 1 (cooldown on day 2, resume on day 3) or by skipping days 1 and 2. Memoization computes each (day, holding) pair once.
dfs(day, holding) as the max profit from day onwards.day >= n, return 0.prices[day] + dfs(day + 2, false), because selling triggers cooldown) or hold (dfs(day + 1, true)).-prices[day] + dfs(day + 1, true)) or skip (dfs(day + 1, false)).(day, holding).dfs(0, false).The call dfs(0, false) branches into buy and skip, and memoization fills the table from the last day backwards. Writing holding = 0 for "not holding" and holding = 1 for "holding", and using dfs(day, ·) = 0 for day >= 5, the cached values are:
The function returns dfs(0, 0) = 3. The winning path reads off the choices: buy at day 0 (price 1), sell at day 1 (price 2), cooldown on day 2, buy at day 3 (price 0), sell at day 4 (price 2), for a profit of 1 + 2 = 3.
The recursion carries call-stack and memo-lookup overhead. The next approach computes the same answer iteratively, tracking the best profit up to each day instead of from each day onwards, and makes the state machine explicit.
Model each day as one of three states:
The transitions are: from Rest, buy (to Held) or do nothing (stay Rest). From Held, sell (to Sold) or do nothing (stay Held). From Sold, you must cool down (to Rest). Each transition becomes one recurrence, computed for every day.
The three states cover every situation a day can be in, and splitting "not holding" into Sold and Rest is what encodes the cooldown: buying is only allowed from Rest, and the only way out of Sold is one day of rest. No separate cooldown counter is needed. The answer is max(sold[n-1], rest[n-1]), never held[n-1]: ending the last day still holding a stock means money was spent on a purchase that was never recovered, which cannot beat the same plan without that purchase.
held[0] = -prices[0], sold[0] = 0, rest[0] = 0.held[i] = max(held[i-1], rest[i-1] - prices[i])sold[i] = held[i-1] + prices[i]rest[i] = max(rest[i-1], sold[i-1])max(sold[n-1], rest[n-1]).Each recurrence reads only the previous day's values, so the three arrays are unnecessary. The next approach replaces them with three variables.
When a DP recurrence has a look-back window of one day, the arrays collapse to scalar variables: keep one value per state and overwrite it each iteration.
The one detail that matters is update order. Running the three updates in sequence would feed already-overwritten values into later updates: sold needs the old held, and rest needs the old sold. Saving all three old values in temporaries before updating any of them makes the order irrelevant.
held = -prices[0], sold = 0, rest = 0.prevHeld = held, prevSold = sold, prevRest = rest.held = max(prevHeld, prevRest - prices[i])sold = prevHeld + prices[i]rest = max(prevRest, prevSold)max(sold, rest).The three-state machine is not the only O(1)-space formulation. The cooldown can also be encoded with the usual two states plus a look-back of two days.
Keep the two states from the unrestricted trading problem: hold[i], the best profit after day i while holding a stock, and free[i], the best profit after day i while not holding one. The cooldown only affects buying: if you buy on day i, your most recent sell happened on day i-2 at the latest. So the buy transition reads the not-holding profit from two days back:
hold[i] = max(hold[i-1], free[i-2] - prices[i])free[i] = max(free[i-1], hold[i-1] + prices[i])Reading only free[i-2] is safe even though sells from day i-3 and earlier also qualify: free is non-decreasing (each free[i] takes a max with free[i-1]), so free[i-2] already includes the best of every earlier day.
This collapses Approach 2's Sold and Rest into a single free state. Three scalars are enough: hold, free, and prevFree, the value of free two days back.
hold = -prices[0], free = 0, prevFree = 0 (the not-holding profit before day 0 is 0).i from 1 to n-1:newHold = max(hold, prevFree - prices[i])newFree = max(free, hold + prices[i])prevFree = free, then hold = newHold, free = newFree.free.Day 3 is where the look-back matters. The buy uses prevFree = 1, the profit available by day 1, rather than free = 2, which includes a sell on day 2 that would make a day 3 buy illegal.