Bitmask DP - Intuition Guide¶
The Mental Model: Binary Light Switches¶
Imagine a control panel with n light switches. Each switch can be ON (1) or OFF (0). The entire panel state is just a sequence of bits:
Panel with 4 switches: [OFF, ON, ON, OFF]
As a number: 0 1 1 0 = 6 in binary
Switch 0 β bit 0 (2^0 = 1)
Switch 1 β bit 1 (2^1 = 2)
Switch 2 β bit 2 (2^2 = 4)
Switch 3 β bit 3 (2^3 = 8)
This is the core of Bitmask DP: we represent "which items are selected" as an integer, and use bit operations to efficiently track and transition between states.
Why Bitmasks?¶
The naive approach to tracking subsets would use sets or arrays:
With bitmasks, the same operations are O(1):
# Fast: bit operations
visited = 0b100101 # {0, 2, 5}
if not (visited & (1 << 3)):
visited |= (1 << 3)
The Bit Operation Toolkit¶
Think of these as your "subset operations":
Operation Code Meaning
βββββββββββββββββββββββββββββββββββββββββββββ
Add i mask | (1 << i) Put i in the set
Remove i mask & ~(1 << i) Take i out
Check if i in mask & (1 << i) Is i present?
Toggle i mask ^ (1 << i) Flip i's membership
Full set (1 << n) - 1 All n elements
Empty set 0 Nothing selected
Count elements bin(mask).count('1') Set size
Pattern 1: Subset Enumeration¶
The insight: Every integer from 0 to 2^n - 1 represents a unique subset.
n = 3, elements = [A, B, C]
mask=0 (000): {} mask=4 (100): {C}
mask=1 (001): {A} mask=5 (101): {A, C}
mask=2 (010): {B} mask=6 (110): {B, C}
mask=3 (011): {A, B} mask=7 (111): {A, B, C}
The code writes itself:
Pattern 2: BFS with Bitmask State (TSP-style)¶
The problem: Find the shortest path visiting ALL nodes in a graph.
The insight: State = (current_node, set_of_visited_nodes)
State (node=2, visited=0b101)
= "I'm at node 2, having visited nodes 0 and 2"
From here, I can go to node 1:
new_state = (node=1, visited=0b101 | 0b010)
= (node=1, visited=0b111)
= "Now at node 1, visited {0, 1, 2}"
Why BFS works: All edges have weight 1, so BFS finds shortest paths.
# Start from EVERY node (we can start anywhere)
for i in range(n):
queue.append((i, 1 << i, 0)) # (node, mask, distance)
while queue:
node, mask, dist = queue.popleft()
for neighbor in graph[node]:
new_mask = mask | (1 << neighbor)
if new_mask == (1 << n) - 1: # All visited!
return dist + 1
if (neighbor, new_mask) not in seen:
queue.append((neighbor, new_mask, dist + 1))
Visual:
Graph: 0 β 1 β 2
Start: (0, 001, 0), (1, 010, 0), (2, 100, 0)
BFS Level 1:
From (0, 001): β (1, 011, 1)
From (1, 010): β (0, 011, 1), (2, 110, 1)
From (2, 100): β (1, 110, 1)
BFS Level 2:
From (1, 011): β (2, 111, 2) β DONE! Distance = 2
Pattern 3: Set Cover DP¶
The problem: Cover all required skills using minimum people. Each person has some subset of skills.
The insight: dp[skill_mask] = minimum team to achieve those skills
Required skills: [Python, Java, SQL]
Person 0: [Python] β mask = 001
Person 1: [Java, SQL] β mask = 110
Person 2: [Python, Java] β mask = 011
dp[000] = [] # No skills, no team needed
dp[001] = [0] # Just Python = Person 0
dp[011] = [2] # Python+Java = Person 2 (better than [0] + someone)
dp[110] = [1] # Java+SQL = Person 1
dp[111] = [1, 2] or [0, 1] # All skills
The transition:
for person_id, person_mask in enumerate(person_masks):
for current_mask, team in list(dp.items()):
new_mask = current_mask | person_mask
if new_mask not in dp or len(team) + 1 < len(dp[new_mask]):
dp[new_mask] = team + [person_id]
The Constraint: Why n β€ 20?¶
Bitmask DP has exponential complexity. Here's the reality:
n 2^n Feasibility
ββββββββββββββββββββββββββββββ
10 1,024 β Fast
15 32,768 β OK
18 262,144 β Borderline
20 1,048,576 β Slow but doable
22 4,194,304 β Too slow
25 33,554,432 β Way too slow
When you see n β€ 20 in constraints, think "bitmask DP is probably intended."
Common Mistakes¶
Mistake 1: Wrong bit check¶
Mistake 2: Forgetting node in TSP state¶
β State = just the visited mask
(Same mask can be reached from different nodes!)
β
State = (current_node, visited_mask)
Mistake 3: Single-source BFS for TSP¶
β queue = [(0, 1, 0)] # Start only from node 0
β
# Start from ALL nodes
for i in range(n):
queue.append((i, 1 << i, 0))
Mistake 4: Modifying dict during iteration¶
β for mask, team in dp.items():
dp[new_mask] = ... # Modifying during iteration!
β
for mask, team in list(dp.items()): # Copy to list first
dp[new_mask] = ...
Quick Pattern Recognition¶
| Clue | Pattern |
|---|---|
| "All subsets" or "power set" | Subset Enumeration |
| "Visit all nodes", n β€ 12 | BFS + Bitmask |
| "Minimum team to cover all", m β€ 16 | Set Cover DP |
| "Partition into k equal groups" | Bitmask + DP |
| "Assign n tasks to n workers" | Bitmask permutation |
The State Transition Mental Model¶
Think of bitmask DP as navigating a hypercube:
n=3: 8 states (vertices of a cube)
011 -------- 111
/ | / |
001 -------- 101 |
| | | |
| 010 -------|- 110
| / | /
000 -------- 100
Transitions: Add one element = move along an edge
Full mask (111) = goal state
Every problem is about finding the best path from some starting vertex to the target vertex.
Visual Summary¶
BITMASK DP PATTERNS:
1. SUBSET ENUM 2. BFS + BITMASK 3. SET COVER DP
mask=0 to 2^n-1 (node, mask) dp[mask] = team
β β β
decode to subset BFS layers person by person
β β β
collect results first full_mask min team for full
Bitmask DP is about encoding set membership as integers for O(1) transitions. The key is recognizing when your state is "which items are selected" and n is small enough (β€ 20).