Best Time to Buy and Sell Stock with Cooldown

Difficulty: Medium

You're given the price of a stock on each of n days. You may buy and sell as many times as you like, but you can only hold one share at a time (you must sell before buying again), and after you sell, you must wait a full day before you're allowed to buy again — a cooldown.

Find the maximum total profit you can make.

Examples

Input: prices = [1,2,3,0,2]
Output: 3

Buy at 1, sell at 2 (profit 1), cooldown, buy at 0, sell at 2 (profit 2). Total: 3.

Input: prices = [1]
Output: 0

There's no second day to sell on, so the best move is to do nothing.

Constraints

  • 1 <= prices.length <= 5000

  • 0 <= prices[i] <= 1000

Approach

Instead of tracking single "best profit so far," track three separate running totals for each day: the best profit if you're currently holding a share, the best profit if you just sold today, and the best profit if you're resting (free to buy, not in a forced cooldown).

Each day's three values only depend on the previous day's three values: - holding today is either the same as yesterday (do nothing), or you bought today using yesterday's resting profit. - just sold today only makes sense if you were holding yesterday, plus today's price. - resting today is the better of resting yesterday, or having just sold yesterday (the cooldown day has passed).

The final answer is the better of "just sold" and "resting" on the very last day — you'd never want to end while still holding a share.

Solutions

Brute Force — Recursion

At each day, recursively try both choices allowed by the current state (holding or not): do nothing, or buy/sell. Selling jumps two days ahead to account for the cooldown. Correct, but re-explores the same (day, state) pairs over and over.

function maxProfit(prices) {
  const n = prices.length;

  function solve(day, holding) {
    if (day >= n) return 0;

    let best = solve(day + 1, holding); // do nothing today

    if (holding) {
      best = Math.max(best, prices[day] + solve(day + 2, false)); // sell, then cooldown tomorrow
    } else {
      best = Math.max(best, -prices[day] + solve(day + 1, true)); // buy
    }

    return best;
  }

  return solve(0, false);
}

Time: O(2^n) · Space: O(n) — recursion depth

Optimal — Bottom-Up 2D Table

Build a table with one row per day and three columns — holding, just sold, and resting — where each day's row is computed purely from the previous day's row.

function maxProfit(prices) {
  const n = prices.length;
  if (n === 0) return 0;

  const HOLD = 0, SOLD = 1, REST = 2;
  const table = Array.from({ length: n }, () => new Array(3).fill(0));

  table[0][HOLD] = -prices[0];
  table[0][SOLD] = -Infinity; // can't have sold yet on day 0
  table[0][REST] = 0;

  for (let day = 1; day < n; day++) {
    table[day][HOLD] = Math.max(table[day - 1][HOLD], table[day - 1][REST] - prices[day]);
    table[day][SOLD] = table[day - 1][HOLD] + prices[day];
    table[day][REST] = Math.max(table[day - 1][REST], table[day - 1][SOLD]);
  }

  return Math.max(table[n - 1][SOLD], table[n - 1][REST]);
}

Time: O(n) · Space: O(n) — can be reduced to O(1) by keeping only the previous day's three values