Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Dynamic Programming

Builds on Linear DP, which you haven’t finished yet.

P2MediumMental model~4 min speedrun

State Machine DP (Buy/Sell Stock)

Grows into
Last-Char Counting DP
The problem

Best Time to Buy and Sell Stock II (LC 122)(opens leetcode.com)

You get an integer array prices: on day i, one share of a stock costs prices[i]. On each day you may buy a share, sell the share you own, or both, but you can never own more than one share at a time. Selling and then buying again on the same day is allowed, and there is no limit on the number of trades.

Return the largest total profit you can make. If no trade helps, the answer is 0.

Example 1
Input:prices = [7,1,5,3,6,4]
Output:7
Buy on day 1 at 1 and sell on day 2 at 5 for +4, then buy on day 3 at 3 and sell on day 4 at 6 for +3. Total 7.
701152336445buysellbuysell

State Machine DP

states (hold, sold, rest) ➔ day-by-day transitions ➔ best final state
Spot it when: At every step you are in one of a few modes with rules for moving between them: buying and selling with cooldowns or fees, colors that can't repeat, a limited number of transactions.
State Machine DP
Python
1def max_profit(prices: list[int]) -> int:
2 """Best Time to Buy and Sell Stock II: 2-state machine, unlimited transactions.
3 States: hold (own one share), cash (own no share)
4 Time: O(N) | Space: O(1)
5 """
6 # hold, cash: best profit so far while owning one share (hold) or owning none (cash)
7 hold = -prices[0]
8 cash = 0
9 
10 for i in range(1, len(prices)):
11 p = prices[i]
12 # 1. Transition Both States From Yesterday:
13 # hold keeps the share or buys today (cash - p); cash stays out or sells today (hold + p).
14 # Both right-hand sides read yesterday's hold and cash, before either is overwritten.
15 hold, cash = max(hold, cash - p), max(cash, hold + p)
16 
17 # Ending while still holding a share is never better than ending with it sold.
18 return cash
Invariant: After each day, hold is the best profit so far while owning one share and cash the best profit so far while owning none.

At the end of each day you are in exactly one of two states, owning a share (hold) or not (cash), and each state's best profit follows only from yesterday's two values: hold = max(hold, cash - p) keeps the share or buys today, and cash = max(cash, hold + p) stays out or sells today. Every legal run of buys and sells is a path through these states and each line keeps only the best way to arrive, so the last cash is the best profit of all; a cooldown, a fee or a trade limit just adds a state or a term, as long as every update reads yesterday's values. The trap is the start: hold = -prices[0], not 0, because hold = 0 means owning a share you never paid for, and selling it would add its full price to cash as profit.

See it run

State Machine DP on prices [7, 1, 5, 3, 6, 4], step by step

hold -7 · cash 0

hold -7 · cash 0

hold -1 · cash 0

hold -1 · cash 0

hold -1 · cash 4

hold -1 · cash 4

hold 1 · cash 4

hold 1 · cash 4

hold 1 · cash 7

hold 1 · cash 7

hold 3 · cash 7

hold 3 · cash 7 · answer 7

Each column is a day. The hold row and the cash row show the best value at the end of that day.

  • p the price of the current day
  • read yesterday's hold and cash, read first
  • write the day whose hold and cash are written

hold = -prices[0] = -7, because buying on day 0 costs 7. cash = 0, because nothing has been sold.

Day 1, p = 1. The new hold is max(-7, 0 - 1) = -1. The new cash is max(0, -7 + 1) = 0.

Write both new values together. Day 1 gets hold = -1 and cash = 0.

Day 2, p = 5. The new hold is max(-1, 0 - 5) = -1. The new cash is max(0, -1 + 5) = 4.

Write both new values together. Day 2 gets hold = -1 and cash = 4.

Day 3, p = 3. The new hold is max(-1, 4 - 3) = 1. The new cash is max(4, -1 + 3) = 4.

Write both new values together. Day 3 gets hold = 1 and cash = 4.

Day 4, p = 6. The new hold is max(1, 4 - 6) = 1. The new cash is max(4, 1 + 6) = 7.

Write both new values together. Day 4 gets hold = 1 and cash = 7.

Day 5, p = 4. The new hold is max(1, 7 - 4) = 3. The new cash is max(7, 1 + 4) = 7.

Write both new values together. Day 5 gets hold = 3 and cash = 7.

The function returns cash = 7. Ending while holding a share is never better than ending with it sold.

price
day 07
day 11
day 25
day 33
day 46
day 54
hold
-7write
cash
0write
1 / 12
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Starting with hold = 0 instead of hold = -prices[0]

    Mistake
    hold = 0
    Fix
    hold = -prices[0]

    What happens: hold is the best profit while owning one share. hold = 0 means owning a share that cost nothing. Selling it adds its full price to cash as profit. Buying on day 0 costs prices[0], so hold = -prices[0].

    Example: prices = [5, 3]
    Mistake: returns 3Fix: returns 0
  2. 2

    Gotcha 2: Buying from yesterday's cash when a cooldown applies

    Mistake
    hold = max(hold, cash - p)
    Fix
    hold = max(hold, cash_before - p)

    What happens: In Best Time to Buy and Sell Stock with Cooldown (LC 309), you cannot buy on the day after a sale. So a buy must start from the cash of two days back. Keep that value in its own variable, cash_before. Every update must read the old values of the previous day, as the tuple assignment does.

    Example: Best Time to Buy and Sell Stock with Cooldown (LC 309): prices = [1, 2, 3, 0, 2]
    Mistake: returns 4Fix: returns 3
  3. 3

    Gotcha 3: Charging the fee on both the buy and the sale

    Mistake
    hold = max(hold, cash - p - fee)
    cash = max(cash, hold + p - fee)
    Fix
    hold = max(hold, cash - p)
    cash = max(cash, hold + p - fee)

    What happens: In Best Time to Buy and Sell Stock with Transaction Fee (LC 714), one transaction is a buy and a sale. The fee is paid once per transaction. Charge it on the sale or on the buy, never on both.

    Example: Best Time to Buy and Sell Stock with Transaction Fee (LC 714): prices = [1, 3, 2, 8, 4, 9], fee = 2
    Mistake: returns 4Fix: returns 8
  4. 4

    Gotcha 4: Using two states when at most k transactions are allowed

    Mistake
    hold, cash = max(hold, cash - p), max(cash, hold + p)
    Fix
    buy[j] = max(buy[j], sell[j - 1] - p)
    sell[j] = max(sell[j], buy[j] + p)

    What happens: Best Time to Buy and Sell Stock III (LC 123) allows at most 2 transactions. Best Time to Buy and Sell Stock IV (LC 188) allows at most k. hold and cash do not record how many transactions were used. Add the count to the state, with buy[j] and sell[j] for j from 1 to k.

    Example: Best Time to Buy and Sell Stock III (LC 123): prices = [3, 3, 5, 0, 0, 3, 1, 4]
    Mistake: returns 8Fix: returns 6
Not this one if…
  • Each step is only take or skip, with no mode that carries over to the next step, as in House Robber (LC 198): the best answers for the last two indices already say everything. See House Robber

Deep Dive

Why two numbers are enough

At the end of a day you either own one share or you own none. There is no third case. So two numbers describe every possible history: hold and cash.

hold is the best profit so far that ends the day owning one share. cash is the best profit so far that ends the day owning none. Profit already counts the money spent on buying, so hold can be negative.

The way the shares were bought and sold in the past does not change what you may do tomorrow. Only the mode matters: you can sell if you own a share, and you can buy if you do not. This is why the past can be reduced to one best value per mode.

Why each update line is correct

hold today comes from two sources. Either you already owned the share yesterday, which gives hold, or you bought it today from a no-share day, which gives cash - p. The better of the two is max(hold, cash - p).

cash today also comes from two sources. Either you owned nothing yesterday, which gives cash, or you sold today, which gives hold + p. The better of the two is max(cash, hold + p).

Every legal sequence of buys and sells is a path through these two modes. Each line keeps only the best way to arrive in its mode, and a better arrival can never lead to a worse finish. So the last cash is the best profit over all legal sequences.

Example: for prices = [7, 1, 5, 3, 6, 4], the pairs (hold, cash) after days 1 to 5 are (-1, 0), (-1, 4), (1, 4), (1, 7) and (3, 7). The answer is 7, which is buy at 1 and sell at 5, then buy at 3 and sell at 6.

Why the tuple assignment matters

The line hold, cash = max(hold, cash - p), max(cash, hold + p) reads the old hold and the old cash on the right side before it writes either one. Both new values come from yesterday.

For unlimited transactions, writing the two lines one after the other gives the same answer. A buy and a sale on the same day earns 0, so reading today's hold does no harm. That is a property of this one problem and not a rule.

In Best Time to Buy and Sell Stock with Cooldown (LC 309), a buy must start from the cash of two days back. A value that was already overwritten gives a wrong result. So read all old values first, and write them together.

The cost: modes times days

The card's complexity line is O(N) time and O(1) space. There are 2 modes and N days. Each day does a fixed amount of work for each mode, so the time is 2 * N steps.

Only yesterday's values are read. So two variables replace a table of N rows, and the space is constant.

More modes cost more per day, not more days. A cooldown adds one mode. A limit of k transactions adds k pairs of modes, so Best Time to Buy and Sell Stock IV (LC 188) takes O(N * k) time. For a fee, as in Best Time to Buy and Sell Stock with Transaction Fee (LC 714), the number of modes stays 2 and only one term changes.

60-Second Pitch

"At the end of each day I am either holding one share or holding none. So I keep two numbers: hold and cash. Each one is the best profit so far that ends the day in that mode. Tomorrow's hold is the better of keeping the share and buying from cash. Tomorrow's cash is the better of staying out and selling from hold. Both read yesterday's values, so I assign them together. The start is hold = -prices[0], because buying on day 0 costs money. I return cash at the end. That is O(N) time and O(1) space. If the problem adds a cooldown or a limit of k trades, I add one mode or k pairs of modes, and the same idea still works."

Common Questions

Why do you return cash and not the larger of hold and cash?

At the end, a share that is still owned has no value for profit. Selling it on the last day gives cash >= hold. So max(hold, cash) always equals cash, and return cash is enough.

How do you add a cooldown of one day?

A buy must start from the cash of two days back. Keep that value in a variable such as cash_before. Each day, compute the new hold from cash_before - p, then move cash into cash_before. For [1, 2, 3, 0, 2] the answer is 3.

How do you handle at most k transactions?

Add the number of completed buys to the state. Keep buy[j] and sell[j] for j from 1 to k. Use buy[j] = max(buy[j], sell[j - 1] - p) and sell[j] = max(sell[j], buy[j] + p). The time is O(N * k) and the space is O(k).

Is a greedy solution also correct for Best Time to Buy and Sell Stock II (LC 122)?

Yes. For unlimited trades without a fee or a cooldown, adding every positive daily rise gives the same answer. The state machine is the general method. It still works when a fee, a cooldown or a limit makes the greedy rule wrong.

What is the time and space complexity, and why?

O(N) time and O(1) space. There are 2 modes and N days, and each day does constant work for each mode. Only yesterday's two values are read, so no table is stored.

SWE Reasoning

Spot it

  • At each step you are in one mode out of a few: owning or not owning, or one of a small set of colors.
  • The rules for the next step depend only on the current mode, for example you can sell only when you own a share.
  • The problem adds a rule that looks at the previous step: a cooldown day, a fee on each trade, a color that may not repeat, or a limit on the number of trades.
  • A brute force would try buy, sell or wait on every day. That is 3 choices per day and 3^N paths, while the number of different modes is tiny.
  • You want one best total, such as maximum profit or minimum cost, and not a list of the choices.

The invariant, step by step

  1. Before the loop, hold = -prices[0] and cash = 0. This is the state after day 0. Buying costs prices[0], and doing nothing earns 0.
  2. A new price p arrives. The loop reads hold and cash as they were at the end of yesterday.
  3. It computes the new hold as the better of keeping the share (hold) and buying today (cash - p).
  4. It computes the new cash as the better of staying out (cash) and selling today (hold + p).
  5. Both new values are assigned together. Now hold and cash describe the end of today, and nothing from yesterday is still needed.
  6. After the last day, cash is the best profit with no share owned. Selling the share on the last day is never worse than keeping it, so return cash is the answer.

Traps

  • Wrong start. hold = 0 means owning a share that cost nothing. A later sale adds its full price as profit. For prices = [5, 3], this returns 3 instead of 0.
  • Reading an overwritten value. In Best Time to Buy and Sell Stock with Cooldown (LC 309), reading today's cash instead of cash_before allows a buy right after a sale. For [1, 2, 3, 0, 2] the result is 4 instead of 3.
  • Fee on both sides. One transaction is a buy plus a sale, and the fee is paid once. In Best Time to Buy and Sell Stock with Transaction Fee (LC 714), charging it twice gives 4 instead of 8 for [1, 3, 2, 8, 4, 9] with fee = 2.
  • Too few modes. With a limit on transactions, hold and cash do not count how many trades were used. For [3, 3, 5, 0, 0, 3, 1, 4] and at most 2 trades, two modes return 8 instead of 6. Best Time to Buy and Sell Stock III (LC 123) needs the count in the state.
  • Returning hold. hold is the best profit while still owning a share, so it is never better than cash at the end. Return cash.

Practice

Canonical LeetCode:Best Time to Buy and Sell Stock II (LC 122)Best Time to Buy and Sell Stock with Cooldown (LC 309)Best Time to Buy and Sell Stock with Transaction Fee (LC 714)Best Time to Buy and Sell Stock III (LC 123)Best Time to Buy and Sell Stock IV (LC 188)Best Time to Buy and Sell Stock (LC 121)Paint House (LC 256)Paint House II (LC 265)
Theory Context•Dynamic Programming

Builds on Linear DP, which you haven’t finished yet.

P2MediumMental model~4 min speedrun

State Machine DP (Buy/Sell Stock)

Grows into
Last-Char Counting DP
The problem

Best Time to Buy and Sell Stock II (LC 122)(opens leetcode.com)

You get an integer array prices: on day i, one share of a stock costs prices[i]. On each day you may buy a share, sell the share you own, or both, but you can never own more than one share at a time. Selling and then buying again on the same day is allowed, and there is no limit on the number of trades.

Return the largest total profit you can make. If no trade helps, the answer is 0.

Example 1
Input:prices = [7,1,5,3,6,4]
Output:7
Buy on day 1 at 1 and sell on day 2 at 5 for +4, then buy on day 3 at 3 and sell on day 4 at 6 for +3. Total 7.
701152336445buysellbuysell

State Machine DP

states (hold, sold, rest) ➔ day-by-day transitions ➔ best final state
Spot it when: At every step you are in one of a few modes with rules for moving between them: buying and selling with cooldowns or fees, colors that can't repeat, a limited number of transactions.
State Machine DP
Python
1def max_profit(prices: list[int]) -> int:
2 """Best Time to Buy and Sell Stock II: 2-state machine, unlimited transactions.
3 States: hold (own one share), cash (own no share)
4 Time: O(N) | Space: O(1)
5 """
6 # hold, cash: best profit so far while owning one share (hold) or owning none (cash)
7 hold = -prices[0]
8 cash = 0
9 
10 for i in range(1, len(prices)):
11 p = prices[i]
12 # 1. Transition Both States From Yesterday:
13 # hold keeps the share or buys today (cash - p); cash stays out or sells today (hold + p).
14 # Both right-hand sides read yesterday's hold and cash, before either is overwritten.
15 hold, cash = max(hold, cash - p), max(cash, hold + p)
16 
17 # Ending while still holding a share is never better than ending with it sold.
18 return cash
Invariant: After each day, hold is the best profit so far while owning one share and cash the best profit so far while owning none.

At the end of each day you are in exactly one of two states, owning a share (hold) or not (cash), and each state's best profit follows only from yesterday's two values: hold = max(hold, cash - p) keeps the share or buys today, and cash = max(cash, hold + p) stays out or sells today. Every legal run of buys and sells is a path through these states and each line keeps only the best way to arrive, so the last cash is the best profit of all; a cooldown, a fee or a trade limit just adds a state or a term, as long as every update reads yesterday's values. The trap is the start: hold = -prices[0], not 0, because hold = 0 means owning a share you never paid for, and selling it would add its full price to cash as profit.

See it run

State Machine DP on prices [7, 1, 5, 3, 6, 4], step by step

hold -7 · cash 0

hold -7 · cash 0

hold -1 · cash 0

hold -1 · cash 0

hold -1 · cash 4

hold -1 · cash 4

hold 1 · cash 4

hold 1 · cash 4

hold 1 · cash 7

hold 1 · cash 7

hold 3 · cash 7

hold 3 · cash 7 · answer 7

Each column is a day. The hold row and the cash row show the best value at the end of that day.

  • p the price of the current day
  • read yesterday's hold and cash, read first
  • write the day whose hold and cash are written

hold = -prices[0] = -7, because buying on day 0 costs 7. cash = 0, because nothing has been sold.

Day 1, p = 1. The new hold is max(-7, 0 - 1) = -1. The new cash is max(0, -7 + 1) = 0.

Write both new values together. Day 1 gets hold = -1 and cash = 0.

Day 2, p = 5. The new hold is max(-1, 0 - 5) = -1. The new cash is max(0, -1 + 5) = 4.

Write both new values together. Day 2 gets hold = -1 and cash = 4.

Day 3, p = 3. The new hold is max(-1, 4 - 3) = 1. The new cash is max(4, -1 + 3) = 4.

Write both new values together. Day 3 gets hold = 1 and cash = 4.

Day 4, p = 6. The new hold is max(1, 4 - 6) = 1. The new cash is max(4, 1 + 6) = 7.

Write both new values together. Day 4 gets hold = 1 and cash = 7.

Day 5, p = 4. The new hold is max(1, 7 - 4) = 3. The new cash is max(7, 1 + 4) = 7.

Write both new values together. Day 5 gets hold = 3 and cash = 7.

The function returns cash = 7. Ending while holding a share is never better than ending with it sold.

price
day 07
day 11
day 25
day 33
day 46
day 54
hold
-7write
cash
0write
1 / 12
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Starting with hold = 0 instead of hold = -prices[0]

    Mistake
    hold = 0
    Fix
    hold = -prices[0]

    What happens: hold is the best profit while owning one share. hold = 0 means owning a share that cost nothing. Selling it adds its full price to cash as profit. Buying on day 0 costs prices[0], so hold = -prices[0].

    Example: prices = [5, 3]
    Mistake: returns 3Fix: returns 0
  2. 2

    Gotcha 2: Buying from yesterday's cash when a cooldown applies

    Mistake
    hold = max(hold, cash - p)
    Fix
    hold = max(hold, cash_before - p)

    What happens: In Best Time to Buy and Sell Stock with Cooldown (LC 309), you cannot buy on the day after a sale. So a buy must start from the cash of two days back. Keep that value in its own variable, cash_before. Every update must read the old values of the previous day, as the tuple assignment does.

    Example: Best Time to Buy and Sell Stock with Cooldown (LC 309): prices = [1, 2, 3, 0, 2]
    Mistake: returns 4Fix: returns 3
  3. 3

    Gotcha 3: Charging the fee on both the buy and the sale

    Mistake
    hold = max(hold, cash - p - fee)
    cash = max(cash, hold + p - fee)
    Fix
    hold = max(hold, cash - p)
    cash = max(cash, hold + p - fee)

    What happens: In Best Time to Buy and Sell Stock with Transaction Fee (LC 714), one transaction is a buy and a sale. The fee is paid once per transaction. Charge it on the sale or on the buy, never on both.

    Example: Best Time to Buy and Sell Stock with Transaction Fee (LC 714): prices = [1, 3, 2, 8, 4, 9], fee = 2
    Mistake: returns 4Fix: returns 8
  4. 4

    Gotcha 4: Using two states when at most k transactions are allowed

    Mistake
    hold, cash = max(hold, cash - p), max(cash, hold + p)
    Fix
    buy[j] = max(buy[j], sell[j - 1] - p)
    sell[j] = max(sell[j], buy[j] + p)

    What happens: Best Time to Buy and Sell Stock III (LC 123) allows at most 2 transactions. Best Time to Buy and Sell Stock IV (LC 188) allows at most k. hold and cash do not record how many transactions were used. Add the count to the state, with buy[j] and sell[j] for j from 1 to k.

    Example: Best Time to Buy and Sell Stock III (LC 123): prices = [3, 3, 5, 0, 0, 3, 1, 4]
    Mistake: returns 8Fix: returns 6
Not this one if…
  • Each step is only take or skip, with no mode that carries over to the next step, as in House Robber (LC 198): the best answers for the last two indices already say everything. See House Robber

Deep Dive

Why two numbers are enough

At the end of a day you either own one share or you own none. There is no third case. So two numbers describe every possible history: hold and cash.

hold is the best profit so far that ends the day owning one share. cash is the best profit so far that ends the day owning none. Profit already counts the money spent on buying, so hold can be negative.

The way the shares were bought and sold in the past does not change what you may do tomorrow. Only the mode matters: you can sell if you own a share, and you can buy if you do not. This is why the past can be reduced to one best value per mode.

Why each update line is correct

hold today comes from two sources. Either you already owned the share yesterday, which gives hold, or you bought it today from a no-share day, which gives cash - p. The better of the two is max(hold, cash - p).

cash today also comes from two sources. Either you owned nothing yesterday, which gives cash, or you sold today, which gives hold + p. The better of the two is max(cash, hold + p).

Every legal sequence of buys and sells is a path through these two modes. Each line keeps only the best way to arrive in its mode, and a better arrival can never lead to a worse finish. So the last cash is the best profit over all legal sequences.

Example: for prices = [7, 1, 5, 3, 6, 4], the pairs (hold, cash) after days 1 to 5 are (-1, 0), (-1, 4), (1, 4), (1, 7) and (3, 7). The answer is 7, which is buy at 1 and sell at 5, then buy at 3 and sell at 6.

Why the tuple assignment matters

The line hold, cash = max(hold, cash - p), max(cash, hold + p) reads the old hold and the old cash on the right side before it writes either one. Both new values come from yesterday.

For unlimited transactions, writing the two lines one after the other gives the same answer. A buy and a sale on the same day earns 0, so reading today's hold does no harm. That is a property of this one problem and not a rule.

In Best Time to Buy and Sell Stock with Cooldown (LC 309), a buy must start from the cash of two days back. A value that was already overwritten gives a wrong result. So read all old values first, and write them together.

The cost: modes times days

The card's complexity line is O(N) time and O(1) space. There are 2 modes and N days. Each day does a fixed amount of work for each mode, so the time is 2 * N steps.

Only yesterday's values are read. So two variables replace a table of N rows, and the space is constant.

More modes cost more per day, not more days. A cooldown adds one mode. A limit of k transactions adds k pairs of modes, so Best Time to Buy and Sell Stock IV (LC 188) takes O(N * k) time. For a fee, as in Best Time to Buy and Sell Stock with Transaction Fee (LC 714), the number of modes stays 2 and only one term changes.

60-Second Pitch

"At the end of each day I am either holding one share or holding none. So I keep two numbers: hold and cash. Each one is the best profit so far that ends the day in that mode. Tomorrow's hold is the better of keeping the share and buying from cash. Tomorrow's cash is the better of staying out and selling from hold. Both read yesterday's values, so I assign them together. The start is hold = -prices[0], because buying on day 0 costs money. I return cash at the end. That is O(N) time and O(1) space. If the problem adds a cooldown or a limit of k trades, I add one mode or k pairs of modes, and the same idea still works."

Common Questions

Why do you return cash and not the larger of hold and cash?

At the end, a share that is still owned has no value for profit. Selling it on the last day gives cash >= hold. So max(hold, cash) always equals cash, and return cash is enough.

How do you add a cooldown of one day?

A buy must start from the cash of two days back. Keep that value in a variable such as cash_before. Each day, compute the new hold from cash_before - p, then move cash into cash_before. For [1, 2, 3, 0, 2] the answer is 3.

How do you handle at most k transactions?

Add the number of completed buys to the state. Keep buy[j] and sell[j] for j from 1 to k. Use buy[j] = max(buy[j], sell[j - 1] - p) and sell[j] = max(sell[j], buy[j] + p). The time is O(N * k) and the space is O(k).

Is a greedy solution also correct for Best Time to Buy and Sell Stock II (LC 122)?

Yes. For unlimited trades without a fee or a cooldown, adding every positive daily rise gives the same answer. The state machine is the general method. It still works when a fee, a cooldown or a limit makes the greedy rule wrong.

What is the time and space complexity, and why?

O(N) time and O(1) space. There are 2 modes and N days, and each day does constant work for each mode. Only yesterday's two values are read, so no table is stored.

SWE Reasoning

Spot it

  • At each step you are in one mode out of a few: owning or not owning, or one of a small set of colors.
  • The rules for the next step depend only on the current mode, for example you can sell only when you own a share.
  • The problem adds a rule that looks at the previous step: a cooldown day, a fee on each trade, a color that may not repeat, or a limit on the number of trades.
  • A brute force would try buy, sell or wait on every day. That is 3 choices per day and 3^N paths, while the number of different modes is tiny.
  • You want one best total, such as maximum profit or minimum cost, and not a list of the choices.

The invariant, step by step

  1. Before the loop, hold = -prices[0] and cash = 0. This is the state after day 0. Buying costs prices[0], and doing nothing earns 0.
  2. A new price p arrives. The loop reads hold and cash as they were at the end of yesterday.
  3. It computes the new hold as the better of keeping the share (hold) and buying today (cash - p).
  4. It computes the new cash as the better of staying out (cash) and selling today (hold + p).
  5. Both new values are assigned together. Now hold and cash describe the end of today, and nothing from yesterday is still needed.
  6. After the last day, cash is the best profit with no share owned. Selling the share on the last day is never worse than keeping it, so return cash is the answer.

Traps

  • Wrong start. hold = 0 means owning a share that cost nothing. A later sale adds its full price as profit. For prices = [5, 3], this returns 3 instead of 0.
  • Reading an overwritten value. In Best Time to Buy and Sell Stock with Cooldown (LC 309), reading today's cash instead of cash_before allows a buy right after a sale. For [1, 2, 3, 0, 2] the result is 4 instead of 3.
  • Fee on both sides. One transaction is a buy plus a sale, and the fee is paid once. In Best Time to Buy and Sell Stock with Transaction Fee (LC 714), charging it twice gives 4 instead of 8 for [1, 3, 2, 8, 4, 9] with fee = 2.
  • Too few modes. With a limit on transactions, hold and cash do not count how many trades were used. For [3, 3, 5, 0, 0, 3, 1, 4] and at most 2 trades, two modes return 8 instead of 6. Best Time to Buy and Sell Stock III (LC 123) needs the count in the state.
  • Returning hold. hold is the best profit while still owning a share, so it is never better than cash at the end. Return cash.

Practice

Canonical LeetCode:Best Time to Buy and Sell Stock II (LC 122)Best Time to Buy and Sell Stock with Cooldown (LC 309)Best Time to Buy and Sell Stock with Transaction Fee (LC 714)Best Time to Buy and Sell Stock III (LC 123)Best Time to Buy and Sell Stock IV (LC 188)Best Time to Buy and Sell Stock (LC 121)Paint House (LC 256)Paint House II (LC 265)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•