150NeetCode 150

113.Best Time to Buy and Sell Stock with Cooldown

Medium2-D Dynamic Programming ยท 3/11

Problem

You are given an array prices where prices[i] is the price of a stock on day i. You may complete as many transactions as you like (buy one share, then sell it), with two restrictions:

  • You may hold at most one share at a time โ€” you must sell before you buy again.
  • After you sell, you must wait one day (cooldown) before buying again.

Return the maximum profit you can achieve.

Examples

Input:  prices = [1, 2, 3, 0, 2]
Output: 3
Explanation: buy day0 @1, sell day1 @2 (+1), cooldown day2, buy day3 @0, sell day4 @2 (+2).
             Selling @3 on day2 would also give +2, but then day3 is a cooldown and the
             cheap buy @0 is lost, so the total would only be 2.
Input:  prices = [1]
Output: 0
Explanation: A single day allows no transaction.
Input:  prices = [6, 1, 3, 2, 4, 7]
Output: 6
Explanation: buy @1 and sell @7 (+6). Splitting it (buy @1, sell @3, cooldown, buy @4, sell @7)
             only earns 2 + 3 = 5.

Constraints

  • 1 <= prices.length <= 5000
  • 0 <= prices[i] <= 1000

๐Ÿ’ก Key Insight

On each day you are in one of a few states โ€” "free to buy" or "holding a share" โ€” and the choice you make depends only on the day and the state, not on the history. That gives a DP over (day, state). The cooldown just means a sell on day i jumps to day i + 2 in the free state. Since each day only looks one or two days ahead, the whole table collapses to three running variables: hold, sold, rest.

Recursion (Brute Force)

TimeO(2^n)SpaceO(n)

Intuition

Let dfs(i, canBuy) be the best profit obtainable from day i onward.

  • If canBuy: either buy today (-prices[i] + dfs(i + 1, false)) or skip (dfs(i + 1, true)).
  • If holding: either sell today (+prices[i] + dfs(i + 2, true) โ€” the +2 is the cooldown) or skip (dfs(i + 1, false)).

Base case: i >= n returns 0.

Algorithm

  1. Define dfs(i, canBuy) as above with the two choices per state.
  2. Return dfs(0, true).

Visualization

flowchart TD
    A["dfs(0, buy)<br/>price 1"] -->|"buy -1"| B["dfs(1, sell)<br/>price 2"]
    A -->|"skip"| C["dfs(1, buy)"]
    B -->|"sell +2"| D["dfs(3, buy)<br/>day 2 is cooldown"]
    B -->|"skip"| E["dfs(2, sell)<br/>price 3"]
    E -->|"sell +3"| F["dfs(4, buy) = 0"]
    D -->|"buy -0"| G["dfs(4, sell)"]
    G -->|"sell +2"| H["dfs(6) = 0"]
    H --> I["path: -1 +2 -0 +2 = 3"]

Code

java
class Solution {
    public int maxProfit(int[] prices) {
        return dfs(prices, 0, true);
    }

    private int dfs(int[] prices, int i, boolean canBuy) {
        if (i >= prices.length) {
            return 0;
        }
        int skip = dfs(prices, i + 1, canBuy); // do nothing today
        if (canBuy) {
            int buy = dfs(prices, i + 1, false) - prices[i];
            return Math.max(buy, skip);
        }
        int sell = dfs(prices, i + 2, true) + prices[i]; // i+1 is the cooldown day
        return Math.max(sell, skip);
    }
}

Complexity

  • Time: O(2^n) โ€” two choices per day.
  • Space: O(n) โ€” recursion depth.

Complexity Comparison

Approach Time Space
Recursion (Brute Force) O(2^n) O(n)
Top-Down Memoization O(n) O(n)
Bottom-Up 2-D Tabulation O(n) O(n)
State Machine O(n) O(1)

Common Pitfalls

  • Selling to i + 1 instead of i + 2 in the recursion โ€” that ignores the cooldown and solves the unrestricted problem.
  • Updating hold, sold, rest in place without saving old values โ€” rest must use yesterday's sold.
  • Allowing a buy from sold instead of rest โ€” that skips the cooldown.
  • Initializing hold = 0 โ€” you cannot be holding a share for free; use a very negative value (not Integer.MIN_VALUE itself, which overflows when you add a price).
  • Using an array of size n + 1 for tabulation โ€” the sell transition reads dp[i + 2], so you need n + 2.