Dynamic Programming Advanced
Lesson, slides, and applied problem sets.
View SlidesLesson
Dynamic 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.
Module Items
Traveling Salesman (Bitmask DP)
Tree Robber (DP on Trees)
Burst Balloons (Interval DP)
0/1 Knapsack
Number of Longest Increasing Subsequences
Advanced DP Checkpoint
Bitmask, tree, interval DP, and knapsack patterns.