Bitmask and Tree DP
Every DP state so far has been a prefix, a grid cell, or a range — always built on top of an array index. Two more shapes break out of that mold entirely: a bitmask, where the state is which subset of items has been used, encoded as the bits of an integer; and a tree, where there’s no table at all — the recursive call stack itself is the DP, with each node combining answers already computed for its children.
What you’ll learn
Section titled “What you’ll learn”- How to represent a subset as the bits of an integer, and the three
bit tricks (
|,&,1 << i) every bitmask DP relies on. - Traveling Salesman (small n) as
dp[mask][i]— the classic bitmask DP. - Why bitmask DP’s state space explodes exponentially and where its practical ceiling is.
- Tree DP: post-order DFS where each call returns what its parent needs, no explicit table required.
- House Robber III, Diameter of Binary Tree, and Maximum Independent Set on a general tree — three problems built on the same “combine children, then decide” shape.
The cue
Section titled “The cue”Visual intuition
Section titled “Visual intuition”Bitmask DP indexes by subset, so it helps to see the subsets themselves enumerated as bit
patterns — the mask axis of the table below is exactly this sequence:
Bit i set means item i is included. That is the whole encoding: dp[mask] indexes this list, `mask | (1 << i)` moves down it, and `(1 << n) - 1` is the last row. For n = 20 this list has about a million entries -- which is precisely why the constraints say n <= 20.
Tree DP builds no table at all. The post-order recursion is the fill order, and each call returns the summary its parent needs:
This is House Robber III's tree. Watch the order nodes are finished in -- leaves first, root last. That ordering is what makes tree DP need no explicit table: by the time a node computes its own answer, both children have already returned theirs.
Bitmask DP: state = (subset, position)
Section titled “Bitmask DP: state = (subset, position)”When a problem depends on which subset of up to ~20 items has been used
— not their count, their specific identity — encode the subset as the
bits of an integer mask. Bit i set means “item i is in the subset.”
Three operations cover everything you need:
mask | (1 << i)— add itemito the subset.mask & (1 << i)— test whether itemiis already in the subset (non-zero means yes).(1 << n) - 1— the “full” mask, every item included.
Traveling Salesman Problem (small n)
Section titled “Traveling Salesman Problem (small n)”Visit every city exactly once, starting and ending at city 0, minimizing
total travel distance. Brute force tries all (n-1)! orderings; bitmask DP
collapses that to 2^n subsets: dp[mask][i] is the cheapest way to start
at city 0, visit exactly the cities in mask, and end at city i.
def tsp(dist):
n = len(dist)
full_mask = (1 << n) - 1
# dp[mask][i] = min cost to start at city 0, visit exactly the cities in
# mask, and end at city i (i must be a member of mask).
dp = [[float("inf")] * n for _ in range(1 << n)]
dp[1][0] = 0 # mask = {0}, currently at city 0, cost so far = 0
for mask in range(1 << n):
for i in range(n):
if dp[mask][i] == float("inf") or not (mask & (1 << i)):
continue
for j in range(n):
if mask & (1 << j):
continue # j already visited in this mask
new_mask = mask | (1 << j)
new_cost = dp[mask][i] + dist[i][j]
if new_cost < dp[new_mask][j]:
dp[new_mask][j] = new_cost
# close the tour: return from the last city back to city 0
return min(dp[full_mask][i] + dist[i][0] for i in range(1, n))
dist = [
[0, 10, 15, 20],
[10, 0, 35, 25],
[15, 35, 0, 30],
[20, 25, 30, 0],
]
print(tsp(dist)) # expect 80 (tour 0 -> 1 -> 3 -> 2 -> 0)The assignment problem (match n workers to n tasks at minimum
total cost) uses the exact same shape: dp[mask] is the min cost to assign
tasks to the workers represented by mask’s set bits, trying every unused
task as “the next one assigned” — one dimension instead of two, since the
worker being assigned is always popcount(mask).
Tree DP: the call stack is the table
Section titled “Tree DP: the call stack is the table”A tree has no natural “index” to loop over, so tree DP skips the explicit table entirely: a post-order DFS visits every node, and each call returns whatever value(s) its parent needs to make its own decision. Because children are always resolved before their parent uses them, the recursion itself enforces the correct DP order.
House Robber III
Section titled “House Robber III”Rob houses arranged in a binary tree; robbing a node forbids robbing its direct children (no such restriction on grandchildren). Each call returns two numbers: the best total if this node is robbed, and the best total if it’s skipped.
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def rob(root):
def dfs(node):
if node is None:
return 0, 0 # (best if robbed, best if skipped) for an empty subtree
left_rob, left_skip = dfs(node.left)
right_rob, right_skip = dfs(node.right)
rob_this = node.val + left_skip + right_skip # can't rob a robbed child
skip_this = max(left_rob, left_skip) + max(right_rob, right_skip)
return rob_this, skip_this
rob_root, skip_root = dfs(root)
return max(rob_root, skip_root)
# 3
# / \
# 2 3
# \ \
# 3 1
tree = Node(3, Node(2, None, Node(3)), Node(3, None, Node(1)))
print(rob(tree)) # expect 7 (rob 3 + 3 + 1, skipping the two 2/3 nodes on the second level)Diameter of Binary Tree
Section titled “Diameter of Binary Tree”The diameter is the longest path between any two nodes, measured in edges — not necessarily through the root. Each call returns its subtree’s height, but along the way it also updates a running best for “the longest path passing through this node”:
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def diameter(root):
best = [0] # mutable cell so the nested function can update it
def height(node):
if node is None:
return 0
left_h = height(node.left)
right_h = height(node.right)
best[0] = max(best[0], left_h + right_h) # longest path THROUGH this node
return 1 + max(left_h, right_h)
height(root)
return best[0]
# 1
# / \
# 2 3
# / \
# 4 5
tree = Node(1, Node(2, Node(4), Node(5)), Node(3))
print(diameter(tree)) # expect 3 (path 4 -> 2 -> 5, or 4 -> 2 -> 1 -> 3) graph BT
N4["4 (leaf: height 1)"] --> N2["2 (height 2, best updated to 1+1=2)"]
N5["5 (leaf: height 1)"] --> N2
N3["3 (leaf: height 1)"] --> N1["1 (height 3, best updated to 2+1=3)"]
N2 --> N1
Binary Tree Maximum Path Sum is the same shape as diameter, but summing
node values instead of counting edges — and clamping each child’s
contribution at max(0, subtree_sum), since a negative subtree should
simply be excluded from the path rather than dragging the sum down.
Maximum Independent Set on a general tree
Section titled “Maximum Independent Set on a general tree”House Robber III generalizes cleanly to any tree given as an adjacency
list: each node still returns (include, exclude), but now sums over
every child instead of just two.
def max_independent_set(n, edges, weight):
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
def dfs(node, parent):
include = weight[node] # take this node -- children must be excluded
exclude = 0 # skip this node -- children are free either way
for child in graph[node]:
if child == parent:
continue
child_include, child_exclude = dfs(child, node)
include += child_exclude
exclude += max(child_include, child_exclude)
return include, exclude
include_root, exclude_root = dfs(0, -1)
return max(include_root, exclude_root)
# tree: 0
# / \
# 1 2
# / \
# 3 4
edges = [(0, 1), (0, 2), (1, 3), (1, 4)]
weight = [1, 1, 1, 1, 1]
print(max_independent_set(5, edges, weight)) # expect 3 (e.g. {2, 3, 4})Dry run
Section titled “Dry run”TSP on four cities, with the distance matrix from the code above. dp[mask][i] is the
cheapest route that starts at 0, has visited exactly mask, and currently sits at i.
Reachable entries only:
mask | cities visited | dp[mask][·] |
|---|---|---|
0001 | 0 | dp[·][0] = 0 — the seed |
0011 | 1 | dp[·][1] = 10 |
0101 | 2 | dp[·][2] = 15 |
1001 | 3 | dp[·][3] = 20 |
0111 | 2 | dp[·][1] = 50 (via 2), dp[·][2] = 45 (via 1) |
1011 | 3 | dp[·][1] = 45 (via 3), dp[·][3] = 35 (via 1) |
1111 | all | dp[·][1] = 70, dp[·][2] = 65, dp[·][3] = 75 |
Closing the tour by returning to city 0: 70 + 10 = 80, 65 + 15 = 80, 75 + 20 = 95. The
answer is 80 — the tour 0 → 1 → 3 → 2 → 0.
- The second index is not decoration.
dp[0111]holds two different values, 50 and 45, because “visited 2, standing at 1” and “standing at 2” have different futures. Drop the position and the recurrence cannot be written: you would not know which distance to add next. - Two distinct tours tie at 80.
0→1→3→2→0and its reverse. Symmetric distance matrices always produce this pair, which is a useful sanity check — and the reason the closingminranges overifrom 1, never 0. - Only 15 of the 64
(mask, i)cells are ever finite. Every reachable state must have bitiset inmaskand bit 0 set (the tour starts at 0), so three quarters of the table is structurally unreachable. Iterating masks in increasing numeric order is enough to guarantee correctness, because adding a city only ever increases the mask — that is the topological order, for free. - The bound is , not . At that is 6 permutations versus 64 cells, so the DP looks worse; at it is billion permutations versus about million cells. That crossover is the whole point.
Tree DP — House Robber III on the tree [3,2,3,null,3,null,1]. Post-order, so leaves
resolve first. Each row is one dfs return:
| node | children’s (rob, skip) | rob_this = val + left_skip + right_skip | skip_this = max(L) + max(R) |
|---|---|---|---|
| leaf 3 (under 2) | (0,0), (0,0) | 3 | 0 |
| 2 | left (0,0), right (3,0) | 2 + 0 + 0 = 2 | 0 + max(3,0) = 3 |
| leaf 1 (under right 3) | (0,0), (0,0) | 1 | 0 |
| right 3 | left (0,0), right (1,0) | 3 + 0 + 0 = 3 | 0 + max(1,0) = 1 |
| root 3 | left (2,3), right (3,1) | 3 + 3 + 1 = 7 | max(2,3) + max(3,1) = 6 |
Answer max(7, 6) = **7** — rob the root and both grandchildren, skipping the middle level.
- Node 2 prefers to be skipped:
skip_this = 3beatsrob_this = 2, because its child is worth more than it is. Returning only a single “best” number would lose that distinction — the parent needsskipspecifically, since it plans to rob itself. - The pair is the state. This is 1-D DP’s
(take, skip)idea moved onto a tree: the parent’s legality constraint is expressed by which of the two numbers it reads, not by any bookkeeping. - No table exists. The “fill order” is post-order traversal, and correctness comes from the recursion, not from a loop ordering you had to get right.
Time and space complexity
Section titled “Time and space complexity”| Problem | Time | Space |
|---|---|---|
| Bitmask DP (TSP, assignment) | ||
| Tree DP (House Robber III, diameter, max independent set) | recursion depth |
When to use it
Section titled “When to use it”- Bitmask DP: the problem depends on which specific subset of a small number of items (roughly ) has been chosen or visited — TSP, assignment problems, “partition into K equal-sum subsets.”
- Tree DP: the input is literally a tree (or reduces to one), and the answer at each node depends on combining results already computed for its children — include/exclude decisions, path sums, subtree heights.
- If a “subset” problem’s
nis too large for2^nto fit in memory, look for a smaller state (a count, a sum, a boolean) instead of the whole subset — that’s usually a sign a different DP shape from earlier lessons applies instead.
The variant map
Section titled “The variant map”| Problem | State | The one thing that changes |
|---|---|---|
TSP (small n) | dp[mask][i] — visited set + current city | the second index is mandatory; without it there is no “where am I” to extend from |
| LC 847 Shortest Path Visiting All Nodes | (node, mask) in a BFS queue | unweighted, so BFS over the state space beats a DP table — see BFS with extra state |
| LC 864 Shortest Path to Get All Keys | (cell, keys_mask) | the mask is keys collected, and the grid supplies the moves |
| LC 526 Beautiful Arrangement | dp[mask] — position is popcount(mask) | the position is implied by how many bits are set, so one dimension suffices |
| LC 1178 Number of Valid Words for Each Puzzle | mask per word | precompute a mask per word, then enumerate submasks of each puzzle |
| LC 698 Partition to K Equal Sum Subsets | dp[mask] + current bucket sum | the bucket sum is derivable from the mask, so it need not be stored |
| LC 1125 Smallest Sufficient Team | dp[mask] → set of people | store the witness alongside the cost when the answer is a set, not a number |
| Submask enumeration | sub = (sub - 1) & mask | iterates every submask of mask in total across all masks, not |
| LC 337 House Robber III | (rob, skip) per node | the pair is the state |
| LC 543 Diameter | height returned, best recorded | split-brain: return one thing, record another |
| LC 124 Max Path Sum | height with max(0, …) clamp | negative subtrees are dropped rather than extended |
| LC 968 Binary Tree Cameras | three states per node | covered-by-self / covered-by-child / not-covered — the same shape with a wider tuple |
| Tree knapsack | dp[node][budget] | a 2-D DP per node, merged child by child |
Interview follow-ups
Section titled “Interview follow-ups”| They ask | What they’re checking | The answer |
|---|---|---|
“The constraints say n <= 20. What does that tell you?” | Reading the problem | That exponential-in-n is expected: states is fine. A bound that small is almost always a bitmask hint, in the same way n <= 500 hints at |
| “Why does TSP’s state need the current city as well as the mask?” | The core of the recurrence | Because the cost of extending depends on where you are standing. dp[mask] alone cannot tell you which distance to add next. The mask says what is done; the index says where you are |
| “What is the complexity, and why is it better than brute force?” | Comparing exponentials | against . At n = 15 that is ~7 million versus ~87 billion. Both are exponential; only one finishes |
“n is now 30” | Boundaries | states is a billion — out of reach. The problem must have different structure: a tree instead of a general graph, a greedy exchange argument, or meet-in-the-middle at |
| “Why does tree DP need no table?” | Understanding the fill order | Because post-order traversal is a valid fill order: children resolve before their parent reads them. The recursion enforces the dependency that a table’s loop order would have to encode manually |
| “Why return a pair in House Robber III?” | The state design | Because the parent’s options depend on which choice the child made. Returning only the best number loses the information the parent needs — node 2 in the trace prefers being skipped (3) over being robbed (2) |
| “Now the tree is a general graph with cycles” | Boundaries again | Tree DP breaks: a node’s subtree is no longer well defined and the recursion may not terminate. You need SCC condensation first, or a different formulation entirely |
| “Enumerate all submasks of a mask” | A specific idiom | sub = mask; while sub: process(sub); sub = (sub - 1) & mask — and note the total over all masks is , not |
Practice — real LeetCode problems
Section titled “Practice — real LeetCode problems”Two ways to escape the array. Tree DP replaces the index with a node and the table with the call stack, returning a tuple of states per node. Bitmask DP packs a set into an integer, which only works because the constraints keep the number of elements around 20.
LC 337 — House Robber III · Medium
Section titled “LC 337 — House Robber III · Medium”Problem. The houses form a binary tree. You cannot rob two directly connected houses. Return the maximum you can take.
Constraints. 1 <= number of nodes <= 10**4, 0 <= node.val <= 10**4.
Examples. [3,2,3,null,3,null,1] gives 7 (3 + 3 + 1) ·
[3,4,5,1,3,null,1] gives 9 (4 + 5)
Editorial · approach, complexity, follow-ups
Tree DP: the recursion is the table, evaluated in post-order because a node’s answer needs its children’s.
The reason a single number per node fails is worth stating precisely. If dfs
returned only “the best for this subtree”, the parent could not tell whether that
best plan robbed the child — and that is exactly the fact the parent needs.
Returning both conditional values fixes it. This is the same idea as the
take/skip pair in LC 198, lifted from a line to a tree.
Time , one visit per node. Space for the stack, which is
on a degenerate tree — and at n = 10**4 a path-shaped tree will blow
Python’s default recursion limit of 1000. Say so; on LeetCode it happens to pass,
but an interviewer may want the iterative post-order or an explicit
sys.setrecursionlimit.
robbedmust use the children’sskippedvalues, never their maxima. Usingmaxthere is the bug that silently robs adjacent nodes.skippedtakes each child’s own max independently. Skipping a node imposes no constraint on either child, and the two children never constrain each other.(0, 0)forNonemakes the leaves work with no special case.[4,1,null,2,null,3]is a left-leaning path 4-1-2-3, and the answer is 7 — the two ends, 4 and 3. Path-shaped trees are what catch a recurrence that quietly assumes two children.- The naive memo on
(node, parent_robbed)also works and is a fine answer; the tuple version just needs no dictionary.
Follow-ups you should expect: “Which houses?” — return the chosen sets, or
re-descend making the same comparisons. “An n-ary tree?” — sum skipped over
all children for the robbed case, and sum the per-child maxima for the skipped
case. “A general graph?” — maximum weight independent set, NP-hard; trees are
tractable because there is exactly one path between any two nodes. “Iteratively?”
— post-order with an explicit stack, or process nodes in reverse BFS order.
“Binary Tree Cameras (LC 968)?” — the same tree-DP shape with three states instead
of two.
LC 847 — Shortest Path Visiting All Nodes · Hard
Section titled “LC 847 — Shortest Path Visiting All Nodes · Hard”Problem. Given an undirected connected graph as an adjacency list, return the length of the shortest walk that visits every node. You may start and stop anywhere, and may revisit nodes and edges.
Constraints. 1 <= n <= 12, the graph is connected, no self-loops or
duplicate edges.
Examples. [[1,2,3],[0],[0],[0]] gives 4 ·
[[1],[0,2,4],[1,3,4],[2],[1,2]] gives 4
Editorial · approach, complexity, follow-ups
Read the constraint first. n <= 12 gives subsets, and
states — tiny. Whenever n is around 12 to 20 and the
problem is about sets, the intended solution is almost always a bitmask.
The key modelling insight is that the node alone is not a state. You may need
to walk back through a node you have already visited, so visited cannot be a
plain per-node flag; it is part of the state. (node, mask) can be reached at most
once usefully, and since every edge has weight 1, BFS over that state graph gives
shortest distances directly — no Dijkstra needed.
Time . Space .
- Seeding all
nstarts at distance 0 is how “start anywhere” is encoded. A single-source BFS from node 0 answers a different, harder question. (1 << i)as the initial mask — you have visited your own starting node.n = 1must return 0: the seed state already has the full mask, and the check happens on dequeue, before any edge is walked. The[[]]case verifies exactly this, and it is why the mask test belongs at the top of the loop body rather than when enqueueing.- Marking
seenat enqueue time, not dequeue time, is what keeps the queue from blowing up with duplicates. This is standard BFS hygiene and still worth saying. - The
return 0at the end is unreachable for connected input — but write it rather than falling off the function.
Follow-ups you should expect: “How is this different from Travelling Salesman?”
— TSP forbids revisiting, so it is a Held-Karp DP over (mask, last) minimising
edge weights; here revisiting is allowed, which is what makes plain BFS correct.
“Weighted edges?” — Dijkstra over the same state space. “Return the walk?” —
store a parent per state and walk back. “n = 40?” — states is out of
reach; that is the meet-in-the-middle or approximation regime. “Must return to the
start?” — add the closing edge, and the start node must then be part of the state.
LC 968 — Binary Tree Cameras · Hard
Section titled “LC 968 — Binary Tree Cameras · Hard”Problem. Place cameras on tree nodes. A camera monitors its own node, its parent and its immediate children. Return the minimum number of cameras needed so every node is monitored.
Constraints. 1 <= number of nodes <= 1000, all node values are 0.
Examples. [0,0,null,0,0] gives 1 ·
[0,0,null,0,null,0,null,null,0] gives 2
Editorial · approach, complexity, follow-ups
Minimum vertex cover’s cousin — a dominating set on a tree — and the tree structure is what makes a greedy bottom-up pass optimal.
The exchange argument, which is the part to say out loud: a camera on a leaf covers the leaf and its parent, while a camera on that parent covers the leaf, the parent and the parent’s parent. So moving any leaf camera up to its parent never covers less. Therefore an optimal solution exists that places cameras only when a child is otherwise exposed — which is exactly the rule the code implements. Place as late as possible, and only under duress.
The three states are the minimum information a parent needs. Two would not do: “covered” has to be split by whether a camera lives here, because a child’s camera covers the parent for free while a merely-covered child does not.
Time . Space .
Nonereturns 1, not 0. If missing children counted as uncovered, every leaf would demand its own camera and you would roughly double the count.- The order of the two checks matters. Exposed children are an obligation and must be handled first; a child’s camera is only a bonus.
- The root needs the final check. Every other node can be rescued by its parent; the root has none, so a state of 0 there costs one more camera.
- A single node answers 1 via that root check — both children are
None, which returns 1, sodfsreturns 0 and the root branch fires. [0,0,0]— a root with two leaf children — answers 1: one camera on the root covers all three. The leaves return 0, the root is forced, and it happens to be the optimum.self.camerasas a counter sidesteps threading a running total through the return value. Anonlocalvariable or a list of length 1 does the same job.
Follow-ups you should expect: “Prove the greedy is optimal?” — the exchange
argument above; interviewers labelling this Hard mostly want that. “As an explicit
DP?” — return a triple of costs per state and minimise, which is the mechanical
version and generalises to weighted nodes. “Weighted cameras?” — the greedy breaks
and you need that triple. “Minimum vertex cover on a tree (a camera covers only its
edges)?” — the same two-state tree DP as House Robber III. “An n-ary tree?” —
identical, with any(child == 0) over the children list. “A general graph?” —
dominating set is NP-hard.
LeetCode problem set
Section titled “LeetCode problem set”Generated from the problem database, so each entry carries its sheet membership and reported companies. Tick them off as you go — progress is saved in this browser, and the Export button writes it to a file you can keep.
Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.
- 543Diameter of Binary TreeeasyThe height-plus-running-max template above
- 337House Robber IIImediumThe include/exclude tree DP above
- 357Count Numbers with Unique Digitsmedium
- 698Partition to K Equal Sum SubsetsmediumBitmask DP over which elements have been assigned to a bucket so far
- 124Binary Tree Maximum Path SumhardDiameter's shape, summing values with negative subtrees clamped to 0
- 847Shortest Path Visiting All Nodeshard
- 864Shortest Path to Get All Keyshard
- 1012Numbers With Repeated Digitshard
- 2376Count Special Integershard
Self-check
Section titled “Self-check”-
A problem's constraints say `n <= 20`. What does that suggest?
Reading constraints as a hint about the intended complexity class is a real skill: n ≤ 20 suggests bitmask, n ≤ 500 suggests O(n³) interval DP, n ≤ 10^5 rules both out.
pch.quizShowAnswer
B — That exponential-in-n is expected — 2^20 is about a million states, so a subset-indexed DP is affordable and probably intended — Reading constraints as a hint about the intended complexity class is a real skill: n ≤ 20 suggests bitmask, n ≤ 500 suggests O(n³) interval DP, n ≤ 10^5 rules both out.
-
Why does TSP's state need the current city in addition to the visited mask?
In the dry run, dp[0111] holds both 50 (standing at city 1) and 45 (standing at city 2) — same visited set, different futures. The mask says what is done; the index says where you are.
pch.quizShowAnswer
B — Because the cost of the next step depends on where you are standing — dp[mask] alone cannot tell you which distance to add — In the dry run, dp[0111] holds both 50 (standing at city 1) and 45 (standing at city 2) — same visited set, different futures. The mask says what is done; the index says where you are.
-
How does O(2^n · n²) compare with brute-force O((n-1)!) for TSP?
Note the DP is actually worse at n = 4 (64 cells versus 6 permutations). Naming where the crossover happens is more convincing than asserting the DP is simply better.
pch.quizShowAnswer
B — Both are exponential, but at n = 15 the DP is about 7 million cells against roughly 87 billion permutations — only one finishes — Note the DP is actually worse at n = 4 (64 cells versus 6 permutations). Naming where the crossover happens is more convincing than asserting the DP is simply better.
-
Why does tree DP need no explicit table?
This is why 'get the loop order right' — the hard part of interval DP — simply does not arise in tree DP. It is also why cycles break it: there would be no valid order.
pch.quizShowAnswer
B — Because post-order traversal is itself a valid fill order — children always resolve before their parent reads them, so the recursion enforces the dependency a table's loop order would have to encode — This is why 'get the loop order right' — the hard part of interval DP — simply does not arise in tree DP. It is also why cycles break it: there would be no valid order.
-
In House Robber III, why return a `(rob, skip)` pair rather than the single best value?
It is the same (take, skip) idea as 1-D House Robber, relocated onto a tree. The legality constraint is expressed by which of the two numbers the parent reads.
pch.quizShowAnswer
B — Because a parent that robs itself needs its children's SKIP values specifically — the single best number loses that. Node 2 in the trace has rob = 2 but skip = 3 — It is the same (take, skip) idea as 1-D House Robber, relocated onto a tree. The legality constraint is expressed by which of the two numbers the parent reads.
-
What is the total cost of enumerating every submask of every mask, and what is the idiom?
The 3^n bound is what makes set-cover and partition-into-subsets problems feasible at n = 16. Getting O(4^n) instead — by testing every mask against every other — is the difference between passing and timing out.
pch.quizShowAnswer
B — O(3^n), via `sub = mask; while sub: …; sub = (sub - 1) & mask` — each bit is independently in-sub, in-mask-only, or out — The 3^n bound is what makes set-cover and partition-into-subsets problems feasible at n = 16. Getting O(4^n) instead — by testing every mask against every other — is the difference between passing and timing out.
Recall card
Section titled “Recall card”- Bitmask cue —
n <= 20in the constraints, and the answer depends on which items are used, not how many. - Encoding — bit
iset = itemiused.mask \| (1 << i)adds,mask & (1 << i)tests,(1 << n) - 1is full. - State design —
dp[mask]when the position is implied (e.g.popcount(mask));dp[mask][i]when you also need where you are, as in TSP. - Iterate masks in increasing numeric order — adding an item only increases the mask, so that ordering is a topological order for free.
- Cost — for TSP-shaped problems. is fine, is not.
- Submasks —
sub = (sub - 1) & mask, across all masks. - Tree DP — no table: post-order DFS, each call returns what its parent needs. Often a
tuple:
(rob, skip),(covered, uncovered),(height, best). - Cycles break tree DP — condense SCCs first, or use a different formulation.
- Bitmask DP encodes a subset as the bits of an integer;
dp[mask][i]answers “best result using exactly this subset, ending ati” — Traveling Salesman and assignment problems share this shape. - The state space is , so bitmask DP is only practical for roughly — know that ceiling before reaching for it.
- Tree DP needs no explicit table: a post-order DFS’s return value carries whatever the parent needs, and the call stack itself enforces “children before parent.”
- House Robber III, Diameter of Binary Tree, and Maximum Independent Set all share the same “combine children, then decide include vs. exclude (or the best split)” pattern.
Between prefix DP, grid/interval DP, and bitmask/tree DP, you now have the full toolkit for recognizing and solving the overwhelming majority of DP-flavored interview and competitive-programming questions — the hard part from here is just practice: spotting which shape a new problem’s state actually needs.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading