36

DP Patterns

Hard DP questions come in a few families. Name the family, and the state, the table and the loop order follow.

advanced~160 min6 simulations11 problems

▶ guided lesson⚡ 1-minute summary↻ 14 recall cards

before this: Dynamic Programming · Arrays & Strings · Binary Search · Intervals

01 · why it exists

Why this exists, and the weight it carries

since1952–67

State-machine DP

Richard Bellman (stages and states); Andrew Viterbi (best path per state per time step)

Bellman's dynamic programming (see the Dynamic Programming chapter) already thinks in stages and states: at each stage you are in some state, and the best future depends only on that state. In 1967 Andrew Viterbi published a decoding algorithm for convolutional codes that keeps, for every state of the encoder at every time step, the best path that ends there — one best number per state per step, the same table the stock problems fill. The stock-trading series itself (I–IV, cooldown, fee) is interview folklore with no inventor.

the problem it solvedChoosing the best sequence of actions when what you may do next depends on the mode you are in — holding or not, cooling down, trades used — without listing every sequence.

its jobs today

  • Backtesting trading rules (illustrative): a simulator replays prices day by day while the strategy is always in one mode — out of the market, holding, or waiting after an exit.
  • Radios: GSM calls, 4G LTE's control channels and Wi-Fi's basic coding mode use convolutional codes, usually decoded with the Viterbi algorithm — the best score per state per time step. (5G uses LDPC and polar codes instead.)
  • Speech and handwriting recognition: in HMM-based recognisers the same per-state, per-step table picks the most likely hidden sequence.

the weight it carries

Illustrative: 10 years of daily prices at about 252 trading days a year (a common rule of thumb) = 2,520 days, at most 5 trades. Acting or waiting each day gives up to 2²⁵²⁰ plans. The machine has 2 × 5 = 10 states: 2,520 × 10 = 25,200 updates.

without it

Every "what I may do next depends on what I did before" question would mean trying every action sequence — exponential in the number of days.

when · why · how

when
Each step you are in one of a few modes, and the legal moves depend on the mode: holding or not, cooling down, k trades used.
why
Time = steps × states × moves: O(n) for cooldown or fee, O(n·k) for k trades. Memory is O(states), because a day reads only the day before.
how
One int per state (hold, sold, rest) or arrays buy[t], sell[t]; save yesterday's values before overwriting; Integer.MIN_VALUE / 2 for impossible states.

02 · plain english

The idea in 30 seconds

Most "hard" DP questions are one of a few families in a new costume. Spot the family and the state almost writes itself.

Each family has a fixed state shape: modes per day (stocks), two prefixes (regex, interleaving), a sum (target sum), jobs sorted by end (scheduling), a range and whose turn it is (games).

The ladder from the previous chapter doesn't change: brute-force recursion → memo → table → squeeze the memory.

picture this

A Java thread is always in exactly one Thread.State (NEW, RUNNABLE, WAITING, TERMINATED, …), and only some moves between them are legal (a TERMINATED thread never runs again). State-machine DP is that picture with a number written on each state: every day you are in one mode (holding a share, just sold, free to buy), each legal move is an arrow, and dp keeps the best profit you could have in each mode. Draw the circles and arrows first; the code is one Math.max per arrow.

03 · remember

What you must know

  • Families, not problems. State machine (modes per step), two-string prefixes, knapsack in disguise, sort + binary search, and game ranges. Each has a state you can write down before any code.
  • State-machine DP: one variable per mode, one Math.max per arrow. Cooldown = HOLD, SOLD, REST. Fee = CASH, HOLD with the fee on the sell arrow. Save yesterday's values before overwriting them.
  • "At most k" goes into the state: buy[t], sell[t] for t = 1..k, so O(n·k) time and O(k) memory. If k ≥ n/2 the limit can't bind — just add every rise.
  • Impossible states start at −∞ (Integer.MIN_VALUE / 2 in Java), never at 0 — a 0 in HOLD would mean a free share.
  • Two strings, new transitions: regex * = zero copies (dp[i][j−2]) or one more copy (dp[i−1][j]); interleaving = the next letter came from s1 (↑) or s2 (←); distinct subsequences = skip this letter or use it (counting with +).
  • Palindromic substrings: 2n − 1 centres, count every successful expansion. O(n²) time, O(1) memory. The pal[i][j] table gives the same count with O(n²) memory; Manacher's O(n) is a follow-up only.
  • Knapsack in disguise: signs + and − split the numbers into two groups. P − N = target and P + N = total, so P = (total + target) / 2 — count subsets with that sum, loop downward.
  • Weighted interval scheduling: sort by end; best[i] = max(skip: best[i−1], take: profit + best[k]), where k = how many jobs end by this start — a binary search. O(n log n). Greedy works only when every job is worth the same.
  • Game DP: both play perfectly, so store one number per range — my best lead. lead[i][j] = max(a[i] − lead[i+1][j], a[j] − lead[i][j−1]). The minus sign is the opponent's best reply.
  • Cost is still states × work per state: stocks n·(states), strings m·n, target sum n·P, scheduling n·log n, games n².

04 · see it

Play with it in 3D

Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.

sim

State machine: buy, sell, cool down

Three boxes are the three modes; the green arrow is the move that won today. The table below keeps every day's numbers so you can trace the best plan back.

loading simulation…
sim

At most k trades: a chain of states

BUY 1 → SELL 1 → BUY 2 → SELL 2 …: each trade adds two states to the chain. Every day walks the chain once.

loading simulation…
sim

Regex table: zero copies or one more

Rows use more of the text, columns more of the pattern. A '*' cell looks two columns left (zero copies) or one row up (eat one more letter).

loading simulation…
sim

Signs become a subset count

First the algebra picks the sum the '+' group needs; then one row of counts is swept downward, once per number.

loading simulation…
sim

Jobs sorted by end + binary search

The timeline shows each job's span. For each job, binary search counts the jobs that finish in time (amber), then best[i] picks skip or take.

loading simulation…
sim

Game DP: one lead per range

Each cell is the best lead the player to move can force on that range. Short ranges first; the last frame replays one perfect game.

loading simulation…

05 · cost

Time & space, explained

State-machine DP over n days
TimeExtra spacewhy
Brute force: try every actionO(2ⁿ)O(n)Act or wait on every day, nothing remembered; the space is the recursion depth.
Cooldown (3 states) / fee (2 states)O(n)O(1)n days × a constant number of states; each day reads only yesterday's numbers.
At most 2 trades (4 states)O(n)O(1)buy1, sell1, buy2, sell2: four max() lines per day.
At most k trades (2k states)O(n·k)O(k)Every day walks t = 1..k with two updates each. If k ≥ n/2, adding every rise is O(n).
The other families
TimeTable spaceOptimised spacewhy
Regex matching (text m, pattern n)O(m·n)O(m·n)O(n)(m + 1)(n + 1) cells, O(1) each; a row reads itself and the row above, so two rows would do.
Interleaving StringO(m·n)O(m·n)O(n)One cell per (i, j) prefix pair; each reads up and left, so one row is enough.
Distinct SubsequencesO(m·n)O(m·n)O(n)Each cell reads up and up-left: one row, filled right to left like 0/1 knapsack.
Palindromic SubstringsO(n²)O(n²)O(1)2n − 1 centres × up to n/2 steps; the pal[i][j] table needs n² booleans, expanding needs none.
Target Sum (P = (total + target) / 2)O(n·P)O(n·P)O(P)n numbers × P sums; pseudo-polynomial, fine because the total is at most 1,000.
Max Profit in Job SchedulingO(n log n)O(n)O(n)The sort, then one O(log n) binary search per job.
Predict the Winner (game on a range)O(n²)O(n²)O(n)n(n + 1)/2 ranges × 2 choices; one row is enough if i runs downward.

06 · java

Raw vs Collections Framework

State machine: a table per day, or a few rolling variables

javaint[][] dp: one row per day, one column per statejava.util.*
class Table {    public int maxProfit(int[] prices) {        int n = prices.length;        final int HOLD = 0, SOLD = 1, REST = 2;        int[][] dp = new int[n + 1][3]; // dp[d][state]: best profit after the first d days, ending in that state        dp[0][HOLD] = Integer.MIN_VALUE / 2; // can't hold a share before day 0 (half of MIN so "+ price" can't overflow)        dp[0][SOLD] = Integer.MIN_VALUE / 2; // can't have sold one either        dp[0][REST] = 0;        for (int d = 1; d <= n; d++) {            int p = prices[d - 1];            dp[d][HOLD] = Math.max(dp[d - 1][HOLD], dp[d - 1][REST] - p); // keep holding, or buy (only from REST)            dp[d][SOLD] = dp[d - 1][HOLD] + p;                             // sell the share we held            dp[d][REST] = Math.max(dp[d - 1][REST], dp[d - 1][SOLD]);      // stay out, or cool down after a sale        }        return Math.max(dp[n][SOLD], dp[n][REST]);    }}
  • Name the states (HOLD, SOLD, REST constants or variables). Bare 0/1/2 indexes are where state-machine bugs hide.
  • Integer.MIN_VALUE / 2 means "impossible". Half of MIN leaves room: with plain MIN_VALUE, hold + p - fee wraps around to a huge positive number whenever p < fee.
  • Save yesterday's values (prevHold, prevSold, prevRest) before writing today's, or every update reads a value you already changed.

Negative sums as states: shift into an array, or key a HashMap

javaint[][] memo, index = sum + totaljava.util.*
class Solution {    public int findTargetSumWays(int[] nums, int target) {        int total = 0;        for (int x : nums) total += x;        // The running sum lives in [-total, total]: shift it by +total to get an array index.        int[][] memo = new int[nums.length][2 * total + 1];        for (int[] row : memo) Arrays.fill(row, -1); // -1 = unknown (counts are never negative)        return ways(nums, 0, 0, target, total, memo);    }     private int ways(int[] nums, int i, int sum, int target, int total, int[][] memo) {        if (i == nums.length) return sum == target ? 1 : 0;        if (memo[i][sum + total] != -1) return memo[i][sum + total];        int count = ways(nums, i + 1, sum + nums[i], target, total, memo)                  + ways(nums, i + 1, sum - nums[i], target, total, memo);        return memo[i][sum + total] = count;    }}
  • A running sum in [−total, total] becomes an index with sum + total; the array needs 2 * total + 1 slots.
  • A HashMap takes any key but boxes every int and hashes on every lookup — noticeably slower than an array index.
  • Pack two ints into one long key ((long) i << 32 | (sum & 0xFFFFFFFFL)) instead of building an i + "," + sum String on every call.
  • Even better: Target Sum's algebra removes negative states entirely — that's the optimal approach.

Last job that fits: a hand-written binary search, or TreeMap.floorEntry

javasorted int[][] + binary searchjava.util.*
class Solution {    public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {        int n = startTime.length;        int[][] jobs = new int[n][];        for (int i = 0; i < n; i++) jobs[i] = new int[] {startTime[i], endTime[i], profit[i]};        Arrays.sort(jobs, (a, b) -> Integer.compare(a[1], b[1])); // by END time        int[] best = new int[n + 1]; // best[i]: max profit using only the first i jobs (best[0] = 0)        for (int i = 1; i <= n; i++) {            int[] job = jobs[i - 1];            int k = countEndingBy(jobs, i - 1, job[0]); // jobs 0..k-1 all end by this start            best[i] = Math.max(best[i - 1], job[2] + best[k]); // skip it, or take it after those k        }        return best[n];    }     /** How many of jobs[0..hi) end at or before `time`? They are sorted by end, so binary search. */    private int countEndingBy(int[][] jobs, int hi, int time) {        int lo = 0;        while (lo < hi) {            int mid = (lo + hi) >>> 1;            if (jobs[mid][1] <= time) lo = mid + 1; // mid fits: the answer is to its right            else hi = mid; // mid ends too late        }        return lo;    }}
  • Keep each job's start, end and profit together (int[][] jobs) before sorting — sorting the three arrays separately scrambles the jobs.
  • Integer.compare(a[1], b[1]) is the safe comparator habit; a[1] - b[1] breaks when values can be far apart in sign.
  • floorEntry(start) returns the entry with the greatest key ≤ start in O(log n): "the best plan that has finished by now".
  • Only put a strictly better total, so map values grow with the key and the floor entry is also the best one.

Game DP: count your total, or count your lead

javathe most the mover can collect (needs prefix sums)java.util.*
/** Game DP, the other way round: the most the player to move can COLLECT from nums[i..j]. */static boolean firstPlayerWins(int[] nums) {    int n = nums.length;    int[] prefix = new int[n + 1];    for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];    int[][] collect = new int[n][n];    for (int i = 0; i < n; i++) collect[i][i] = nums[i];    for (int len = 2; len <= n; len++) {        for (int i = 0; i + len - 1 < n; i++) {            int j = i + len - 1;            int rangeSum = prefix[j + 1] - prefix[i];            // Whatever I leave behind, the opponent collects their best of it; I get the rest of the range.            collect[i][j] = rangeSum - Math.min(collect[i + 1][j], collect[i][j - 1]);        }    }    int mine = collect[0][n - 1];    return mine >= prefix[n] - mine; // my total vs. what's left for the opponent}
  • Total form: collect = rangeSum − min(opponent's best on what you leave). Needs prefix sums; answers "how many points" directly.
  • Lead form: lead = take − lead(rest). One table, no sums, and the sign of lead[0][n − 1] is the winner.
Java API you will actually use
calldoescost
Integer.MIN_VALUE / 2−∞ for impossible states (holding before any buy); leaves room for ± a price without wrappingO(1)
Math.max(keep, move)Every state update: stay where you are, or arrive along an arrowO(1)
Arrays.fill(buy, Integer.MIN_VALUE / 2)Start every "holding share t" state as impossibleO(k)
new boolean[m + 1][n + 1]Two-string table; row 0 / column 0 = the empty prefixO(m·n)
s.charAt(i - 1)The last letter of the prefix of length i (the table is shifted by one)O(1)
Arrays.sort(jobs, (a, b) -> Integer.compare(a[1], b[1]))Sort int[][] rows by their end timeO(n log n)
map.floorEntry(key) / map.lastEntry()TreeMap: greatest key ≤ key (or null) / the largest keyO(log n)
new Integer[n][n] / new Boolean[m + 1][n + 1]Memo where null = unknown (leads can be 0 or negative; matches can be false)O(n²) / O(m·n)
"a".repeat(n)Build long test strings quickly (Java 11+)O(n)

A few modes per step (holding, cooling down, trades used)

→ Named int variables, or buy[]/sell[] arrays

Each day reads only yesterday: O(states) memory, no table.

Two strings: match, merge or count

→ boolean[][] / int[][] over prefixes, then one row

One cell per prefix pair; the row trick needs the dependency arrows (↑, ←, ↖).

Running sums that can be negative

→ Offset array (sum + total), or HashMap<Long, Integer>

Arrays are fastest when the range is small; a map handles anything.

"Last interval that ends before this one starts"

→ Sort by end + binary search, or TreeMap.floorEntry

O(log n) per job instead of scanning back.

Count palindromic substrings

→ Expand around 2n − 1 centres

Same O(n²) as the table, but no n² memory.

Two players, optimal play, on a range

→ int[][] lead filled by range length

One number per range; the minus sign does the opponent's thinking.

07 · recognise

Spot the pattern

Buy and sell with a cooldown, a fee, or at most k trades

State-machine DP: one variable per mode, one max() per arrow

"At most k" of anything (trades, deletions, operations)

Add the count to the state: dp[…][t] for t = 0..k

A pattern with '.' and '' must match the whole* string

Two-string boolean DP; '*' = zero copies (j − 2) or one more (i − 1)

Can s3 be formed by merging s1 and s2, keeping each one's order?

dp[i][j] over two prefixes: the next letter came from s1 or from s2

How many times does t appear as a subsequence of s?

Counting two-string DP: skip s[i] or use it; one row, j downward

Count (or find) palindromic substrings

Expand around all 2n − 1 centres

Put + or − before each number to hit a target; split into two groups by a difference

Subset count with P = (total + target) / 2

Jobs or intervals with a profit, no overlaps, maximise the total

Sort by end + DP + binary search for the last compatible job

All intervals are worth the same

Greedy by earliest end (Intervals chapter) — no DP needed

Two players take turns, "both play optimally"

Game DP: one 'lead' per state, opponent's best comes back with a minus sign

08 · traps & tricks

Mistakes to avoid, tricks to use

traps

  • Reading a state you already overwrote today. In Cooldown, HOLD must use yesterday's REST and SOLD must use yesterday's HOLD — save the previous values first.
  • Starting HOLD at 0. That claims a free share. Use −∞ (Integer.MIN_VALUE / 2) or -prices[0].
  • Integer.MIN_VALUE as −∞, then hold + p - fee with p < fee: it wraps to a huge positive number and wins every max.
  • Regex: treating '*' as a wildcard on its own. It always belongs to the character before it — look at p[j − 1] and p[j − 2] together.
  • Regex: leaving row 0 all false. "ab" matches the empty text, so dp[0][j] = dp[0][j − 2] for a '*'.
  • Interleaving: skipping the m + n == s3.length() check — leftover letters in s3 then go unnoticed.
  • Distinct Subsequences in one row with j going UP: the same letter of s is used twice in one subsequence. Go down, as in 0/1 knapsack.
  • Target Sum: forgetting the parity and |target| ≤ total checks, or a loop that stops before s = x so zeros never double the count.
  • Job Scheduling: < instead of <= (touching jobs are allowed), or binary-searching an array that isn't sorted by the key you search.
  • Game DP: maximising your own score as if the opponent moved randomly. The opponent plays their best — that's the minus sign.

tricks

  • Draw the state machine before any code: circles for modes, arrows for moves. Then write exactly one line per arrow.
  • For two strings, say the state with prefixes in one sentence: "dp[i][j] = can the first i letters of s match the first j of p".
  • Signs, two groups, or a difference in the question? Try the algebra: P + N = total, P − N = target.
  • Bas itna yaad rakho: same value for every job → greedy; different values → sort by end + DP + binary search.
  • Game DP: write "my best lead". The opponent's best lead comes back from the recursion, and you subtract it.
  • Chalo, dry-run on 4–5 days or 3–4 letters first. Every family in this chapter fits on one page of paper.
  • After the table works, look at the dependency arrows (↑ ← ↖) to decide whether one row is enough and which way to loop.

09 · why & how

Questions you might be too shy to ask

How do I tell which DP family a new problem belongs to?

Ask what you must remember to make the next decision. A status that changes over time (holding, cooling down, trades used) → state machine. Positions in two strings → a prefix table. Signs or two groups → subset sum. Intervals with values → sort by end + binary search. Two players alternating → game DP on what's left. The answer to "what must I remember" is the state.

Why is the stock DP called a "state machine"?

Because at the end of each day you are in exactly one mode, and only some moves between modes are legal — like a Java thread's Thread.State. Draw the modes as circles and the legal moves (buy, sell, rest, cool down) as arrows. The DP keeps the best profit for every circle; each arrow becomes one Math.max. Cooldown adds a SOLD circle; a fee puts a cost on the sell arrow; k trades makes a chain of 2k circles.

In Stock III, why is it safe to update buy1, sell1, buy2, sell2 in place on the same day?

Using today's updated buy1 inside sell1 means "buy and sell on the same day", which earns exactly 0. So it can never create fake profit; it only lets a state copy a value it could already have. The same argument covers buy[t] and sell[t] in Stock IV.

Cooldown returns Math.max(sold, rest). Why is hold never the answer?

Ending in HOLD means you paid for a share and never sold it. That value is some earlier REST profit minus a price. REST never goes down over the days, so today's REST is at least as big, without the unsold share. So holding at the end is never better: skip that buy and you have at least as much.

Do I need to memorise all the stock problems?

No. Memorise the machine. Stock I is k = 1, Stock II is unlimited (CASH/HOLD), III is k = 2, IV is general k, Cooldown adds a SOLD state, Fee puts a cost on the sell arrow. If you can draw the circles, you can write any of them in a few minutes.

What's the difference between Regular Expression Matching (LC 10) and Wildcard Matching (LC 44)?

In LC 10, '' repeats the character before it ("a" = zero or more a's). In LC 44, '' alone matches any sequence and '?' matches one character. Same (m + 1) × (n + 1) table; only the '' rule changes: LC 44 uses dp[i][j − 1] (match nothing) || dp[i − 1][j] (eat one more).

When I squeeze a 2D table into one row, how do I know whether j goes up or down?

Look at what a cell reads. ↑ is dp[j] itself — the old value, always safe. If it also reads a cell to the left in the previous row (↖ dp[j − 1], or knapsack's dp[c − w]), go down, so that cell isn't overwritten yet: Distinct Subsequences, 0/1 knapsack. If it reads ← (dp[j − 1] from this row), go up: Interleaving String. Needs both ↖ and ← (LCS)? Keep ↖ in a separate variable before you overwrite it.

Distinct Subsequences' middle counts can overflow even a long. Why does an int solution still work?

The DP only adds. Java's int addition wraps around (it computes modulo 2³²), and sums modulo 2³² are still exact modulo 2³². If the true final answer fits in an int — LeetCode guarantees it — the wrapped result equals it. This only works because nothing compares, divides or takes a max of the middle values.

Expand-around-centre has no table. Why is Palindromic Substrings in a DP chapter?

It uses the DP fact: s[i..j] is a palindrome when its ends match and s[i+1..j−1] is one. The pal[i][j] table stores that for every pair. Expanding from a centre walks the same chain, inside → out, and stops at the first failure, so there is nothing to store. Same idea, same O(n²) time, O(1) memory.

Do I need Manacher's algorithm for interviews?

Rarely. Expand-around-centre (O(n²) time, O(1) memory) is the expected answer for Palindromic Substrings and Longest Palindromic Substring. Mention that Manacher's algorithm reaches O(n) by reusing the mirror image inside a known palindrome — that's usually enough.

Target Sum: why does a 0 in nums double the answer?

+0 and −0 are two different sign choices that give the same sum, and the problem counts both. The code does it by itself: for x = 0 the loop runs s = want down to s >= 0, so ways[s] += ways[s - 0] doubles every count. That's why the loop must go all the way to s >= x, not stop at s > x.

Why can't I use the greedy "earliest end first" from the Intervals chapter for Job Scheduling?

Greedy maximises the number of jobs, not their value. Jobs [1–10] paying 100 and three short jobs inside that time, paying 1 each: greedy takes the three short ones for 3; the best is 100. Once values differ, you need the DP's "skip or take" at every job.

Job Scheduling: why sort by end time and not by start time?

After sorting by end, the jobs that finish by this job's start are always a prefix of the list. best[k] already holds the best plan for that prefix, and one binary search finds k. Sorting by start also works, but then you fill from the right: skip this job, or take it plus the best plan from the first job starting at or after its end.

Why does the game DP store a difference instead of both players' scores?

The game is the same for whoever is to move, so one number — "how far ahead can the mover get" — describes a range completely. When you take nums[i], the opponent becomes the mover on the rest and gets ahead by lead[i + 1][j], so your lead is nums[i] − lead[i + 1][j]. Two scores would need two tables and a turn flag.

10 · practice

Interview problems

Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.

  1. #309
    Best Time to Buy and Sell Stock with Cooldownmust-do
    MediumGoogleBloombergAmazonMicrosoft
  2. #714
    Best Time to Buy and Sell Stock with Transaction Fee
    MediumBloombergGoogleMicrosoftAmazon
  3. #123
    Best Time to Buy and Sell Stock III
    HardMicrosoftAmazonGoogleBloomberg
  4. #188
    Best Time to Buy and Sell Stock IVmust-do
    HardAmazonGoogleMicrosoft
  5. #10
    Regular Expression Matchingmust-do
    HardAirbnbByteDance / TikTokAmazonGoogleMeta+1
  6. #97
    Interleaving Stringmust-do
    MediumAmazonMicrosoftGoogle
  7. #115
    Distinct Subsequences
    HardSalesforceAmazonGoogleBloomberg
  8. #647
    Palindromic Substringsmust-do
    MediumMetaWalmartAmazonMicrosoftGoogle
  9. #494
    Target Summust-do
    MediumAmazonMetaGoogleMicrosoft
  10. #1235
    Maximum Profit in Job Schedulingmust-do
    HardAirbnbAmazonMicrosoftGoogle
  11. #486
    Predict the Winner
    MediumAmazonGoogle

Also practise this here

Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.

  1. #121
    Best Time to Buy and Sell Stock
    Easyin Arrays & Strings

    Stock I: one trade. Its running minimum is the k = 1 case of this chapter's machine.

  2. #122
    Best Time to Buy and Sell Stock II
    Mediumin Arrays & Strings

    Stock II: unlimited trades. Its cash/hold alternative is the two-state machine that Fee and Cooldown extend.

  3. #416
    Partition Equal Subset Sum
    Mediumin Dynamic Programming

    Target Sum's subset count is this problem with + instead of ||.

  4. #5
    Longest Palindromic Substring
    Mediumin Dynamic Programming

    The same 2n − 1 centres as Palindromic Substrings; keep the longest instead of counting.

  5. #72
    Edit Distance
    Mediumin Dynamic Programming

    The parent of every two-string table here: prefixes, base row and column, three neighbours.

  6. #435
    Non-overlapping Intervals
    Mediumin Intervals

    Job Scheduling with every job worth the same: greedy by end time, no DP.

  7. #312
    Burst Balloons
    Hardin Dynamic Programming

    Interval DP filled by range length, like the game table.

  8. #131
    Palindrome Partitioning
    Mediumin Backtracking

    Precompute pal[i][j] from this chapter so every palindrome check is O(1).

11 · revise

The whole chapter on one screen

9 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.

  1. what it is

    Five families: state machine (modes per day), two-string prefixes, knapsack in disguise, sort + binary search, game ranges. Name the family; the state follows.

  2. use it when

    "cooldown / fee / at most k", "pattern with * must match all", "+ or − to hit target", "jobs with profit, no overlap", "both play optimally".

  3. template

    Machine: save yesterday, then hold = max(hold, rest − p), sold = hold + p, rest = max(rest, sold). One line per arrow.

  4. template

    Two strings: dp[i][j] over prefixes. Regex '*': dp[i][j−2] || (fits && dp[i−1][j]). Interleave: from s1 (↑) or from s2 (←).

  5. cost

    Stocks O(n) or O(n·k), memory O(1)/O(k). Two strings O(m·n). Target sum O(n·P). Jobs O(n log n). Game O(n²). Palindromes O(n²), O(1) memory.

  6. trap

    Never read a state you already changed today: save prev values first. Holding starts at −∞ (MIN_VALUE / 2), never 0.

  7. trap

    Weighted jobs: earliest-end greedy fails. Sort by end and binary-search jobs with end ≤ start (<=, not <).

  8. pro tip

    Target Sum: P = (total + target) / 2. Check parity and |target| ≤ total, then count subsets with a downward loop.

  9. pro tip

    Game DP: store my lead, not two scores: max(a[i] − lead[i+1][j], a[j] − lead[i][j−1]). Player 1 wins if it's ≥ 0.

12 · recall

Flip, remember, repeat

card 1 / 14
recall

What is state-machine DP, in one line?

tap or Space to flip ↻

Question 1: What is state-machine DP, in one line?

13 · check yourself

Quick quiz

  1. In Stock with Cooldown (HOLD, SOLD, REST), which state does a buy come from?

  2. Stock IV with k ≥ n/2. What is the answer?

  3. Regex table: p[j − 1] is '*'. Which cells can make dp[i][j] true?

  4. s = "" and p = "a*b*". Does it match?

  5. nums add up to 10 and target = 4. The '+' group must add up to…

  6. One-row Distinct Subsequences: why does j loop from n down to 1?

  7. Jobs sorted by end time. For a job that starts at 5, what does the binary search find?

  8. Why is there a minus in lead[i][j] = nums[i] − lead[i + 1][j]?

  9. How many centres does expand-around-centre try for a string of length n?