Dynamic Programming Advanced
Work through examples, try the reasoning yourself, then put it into practice.
View SlidesIn this lesson
Lesson
5 min readDynamic Programming Advanced
Why this module exists
Basic DP is usually "one index, one recurrence." Advanced DP is about choosing the right state space:
- subsets (bitmasks)
- trees (parent-child constraints)
- intervals (choose last/split point)
- capacity dimensions (knapsack)
- dual-state counting (length + number of ways)
Most failures come from bad state design, not syntax.
Learning goals
By the end, you should be able to:
- derive state from output grain, not from intuition
- choose transition direction that avoids double counting
- map structural constraints (tree, interval, subset) to DP indices
- justify complexity before coding
- debug DP with small invariant checks
A practical DP design loop
Use this loop for every problem:
- Define what one DP cell means in plain language.
- Write valid transitions into that cell.
- Set base cases that make recurrence true at boundaries.
- Choose iteration order so dependencies are already known.
- Add one or two invariants to sanity-check intermediate states.
If you cannot clearly explain step 1, you are not ready to code.
Pattern 1: bitmask DP (TSP)
When n is small (n <= ~20), a bitmask can encode visited nodes.
Canonical TSP state:
dp[mask][i] = minimum cost to start at node 0, visit exactly nodes in mask, and end at node i
Transition:
- from
(mask, i)to new nodejnot inmask nextMask = mask | (1 << j)dp[nextMask][j] = min(dp[nextMask][j], dp[mask][i] + dist[i][j])
Base:
dp[1<<0][0] = 0
Finish:
min_i dp[allVisited][i] + dist[i][0]
Complexity:
O(n^2 * 2^n)time,O(n * 2^n)memory
Pattern 2: DP on trees (take/skip)
For independent-set style constraints on trees ("cannot take adjacent"):
Define two states per node u:
take[u]: best value in subtree ifuis takenskip[u]: best value in subtree ifuis skipped
Recurrence:
take[u] = value[u] + sum(skip[child])skip[u] = sum(max(take[child], skip[child]))
Order:
- post-order traversal (children before parent)
Important practical point:
- with
nup to 200k, iterative traversal is safer than deep recursion in Go.
Handling negative values:
skipallows "take nothing" behavior, so global result can still be0.
Pattern 3: interval DP (burst balloons)
In interval DP, you usually choose the last action inside an interval.
For burst balloons:
- add sentinels:
vals = [1] + nums + [1] - use open interval state:
dp[l][r] = best coins from bursting balloons strictly between l and r
Transition:
- choose
kas last balloon in(l, r) dp[l][r] = max(dp[l][k] + dp[k][r] + vals[l]*vals[k]*vals[r])
Order:
- increase interval length, so smaller intervals are solved first
Complexity:
O(n^3)time,O(n^2)memory
Pattern 4: 0/1 knapsack (backward capacity)
State:
dp[c] = best value achievable with capacity c after processing some prefix
For each item (w, v):
- iterate
cfromcapacitydown tow dp[c] = max(dp[c], dp[c-w] + v)
Why backward?
- prevents reusing the same item multiple times in one iteration
Forward iteration changes semantics to unbounded knapsack.
Pattern 5: count LIS (length + ways)
Need both:
length[i]: LIS length ending aticount[i]: number of LIS of that length ending ati
Initialize:
length[i] = 1count[i] = 1
Transition for each j < i with nums[j] < nums[i]:
- if
length[j] + 1 > length[i]:- better length found
- update length and replace count
- else if equal:
- add counts (
count[i] += count[j])
- add counts (
Final answer:
- sum
count[i]for alliwherelength[i]equals global max length
Complexity:
O(n^2)time,O(n)memory
Choosing the right pattern quickly
Ask:
- Is "which elements already chosen" the core state?
- yes -> bitmask DP
- Is the input a tree and constraints are local on edges?
- yes -> tree DP
- Does score depend on neighbors after removals/splits?
- yes -> interval DP
- Is there a capacity/budget dimension with include/exclude choice?
- yes -> knapsack
- Are you asked for number of optimal subsequences?
- yes -> length + count DP
Debugging checklist by pattern
Bitmask DP:
dp[1<<0][0]initialized?- transitions only to unvisited nodes?
- final return includes edge back to start?
Tree DP:
- parent edge excluded in child loops?
- post-order truly processes children first?
- final answer is
max(take[root], skip[root])?
Interval DP:
- sentinels added correctly?
- interval is open
(l, r)? - iteration by increasing interval length?
Knapsack:
- capacity loop direction is backward?
- base
dpstarts at zeros? w > capacityitems naturally ignored?
Count LIS:
- strict
<, not<=? - count replaced on better length, added on equal length?
- empty input returns 0?
What you will build in this module
tsp-bitmask: subset DP with endpoint statetree-robber: tree DP with take/skipburst-balloons: interval DP with last-burst choiceknapsack-01: backward 1D capacity DPlis-count: dual-state DP for optimal length and count
Focus on writing state meaning in one sentence before coding each solution.
Practice sequence
Use the order below for a guided path, or choose an item to revisit. A pass records a successful attempt; try a fresh example later to check what you can reconstruct.
- Step 1
Traveling Salesman (Bitmask DP)
- Step 2
Tree Robber (DP on Trees)
- Step 3
Burst Balloons (Interval DP)
- Step 4
0/1 Knapsack
- Step 5
Number of Longest Increasing Subsequences
- Step 6
Advanced DP Checkpoint
Work fresh traces, diagnose invalid implementations, and identify model boundaries.