Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Bit Manipulation

Builds on Subsets, which you haven’t finished yet.

P3MediumMental model~3 min speedrun

Bitmask Enumeration (Subset Masks)

Unlocks
Bitmask DP

Binary Watch (LC 401)(opens leetcode.com)

A binary watch shows the time with 10 LEDs. Four LEDs on the top encode the hour (0 to 11) and six LEDs on the bottom encode the minute (0 to 59); each LED is one bit, with the lowest bit on the right.

Given turnedOn, the number of LEDs that are lit, return every time the watch could be showing, in any order. Write the hour without a leading zero ("1:00", never "01:00") and the minute with exactly two digits ("10:02", never "10:2").

Example 1
Input:turnedOn = 1
Output:["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]
One lit LED is a single minute bit (1, 2, 4, 8, 16 or 32 minutes) or a single hour bit (1, 2, 4 or 8 o'clock).
8041221332416586472819hour LEDsminute LEDs

Bitmask Subsets

collection ➔ integer mask ➔ subset states
Spot it when: n is small (roughly 20 or less) and you must try every subset, or a DP state is 'which items are used so far'.
Bitmask Subsets
Python
1def read_binary_watch(turnedOn: int) -> list[str]:
2 """Binary Watch: every set of lit LEDs is one 10-bit mask.
3 Time: O(B * 2^B), B = 10 LEDs | Space: O(1) extra, output not counted
4 """
5 times: list[str] = [] # valid "h:mm" strings found so far
6 
7 # 1. Visit Every Possible Mask:
8 # Each integer 0 to 2^10 - 1 is one assignment of the 10 LEDs (4 hour bits,
9 # 6 minute bits), so looping over that range visits every combination once.
10 for mask in range(1 << 10):
11 if bin(mask).count("1") != turnedOn: # Popcount: how many LEDs are lit
12 continue
13 hour, minute = mask >> 6, mask & 0b111111 # Top 4 bits: hour; low 6 bits: minute
14 
15 # 2. Reject Out-Of-Range Fields:
16 # 4 bits can hold values up to 15 and 6 bits up to 63, but a watch only
17 # shows hours 0-11 and minutes 0-59, so a decoded field outside that range is dropped.
18 if hour < 12 and minute < 60:
19 times.append(f"{hour}:{minute:02d}")
20 return times
21 
22 
23def subsets(nums: list[int]) -> list[list[int]]:
24 """Subsets: every subset of distinct nums, one bitmask per subset.
25 Time: O(N * 2^N) | Space: O(N * 2^N) output
26 """
27 n = len(nums)
28 result: list[list[int]] = [] # one subset per mask
29 
30 # 1. Decode Each Mask Into Its Subset:
31 # Each integer 0 to 2^n - 1 is one subset; bit i of mask tells whether
32 # nums[i] is included, so collecting the items whose bit is on rebuilds that subset.
33 for mask in range(1 << n):
34 result.append([nums[i] for i in range(n) if (mask >> i) & 1])
35 return result
36 
37 
38def enumerate_submasks(mask: int) -> list[int]:
39 """Sub-Mask Enumeration: every subset of the bits set in `mask`, largest first.
40 Time: O(2^k), k = popcount(mask); O(3^n) summed over all masks | Space: O(2^k) output
41 """
42 submasks: list[int] = [] # every non-empty submask found so far, largest first
43 sub = mask
44 
45 # 1. Step Through Every Non-Empty Submask:
46 # (sub - 1) & mask clears the lowest set bit of sub and sets every bit below
47 # it, then masking with mask drops anything outside it, landing on the next smaller submask.
48 while sub > 0:
49 submasks.append(sub)
50 sub = (sub - 1) & mask # Drop the lowest bit, keep only bits inside mask
51 
52 # 2. Add the Empty Set:
53 # The loop above stops once sub reaches 0, so the empty submask is never
54 # appended inside it; add it once here.
55 submasks.append(0)
56 return submasks
Invariant: Bit i of mask is on exactly when item i is in the subset, so each number in range(1 << n) is one subset and every subset appears once.

An integer is a set: bit i is on when item i is chosen, so the subsets of n items are exactly the numbers in range(1 << n), (mask >> i) & 1 says whether item i is in, and bin(mask).count("1") how many items are. Each subset matches one number and each number one subset, so one loop visits every subset exactly once, with no recursion and no duplicates; to visit only the subsets of one mask, step with sub = (sub - 1) & mask, which clears the lowest set bit, sets the bits below it, and keeps only bits of mask. The trap is decoding a packed mask: split it into its fields (hour = mask >> 6, minute = mask & 0b111111) and check each field's range (hour < 12 and minute < 60), because 4 bits reach 15 and 6 bits reach 63.

See it run

Subsets of nums = [1, 2, 3] with one mask per subset

n 3 · masks 8

test (mask >> i) & 1

mask 0 (000) · subset []

mask 1 (001) · subset [1]

mask 2 (010) · subset [2]

mask 3 (011) · subset [1, 2]

mask 4 (100) · subset [3]

mask 5 (101) · subset [1, 3]

mask 6 (110) · subset [2, 3]

mask 7 (111) · subset [1, 2, 3]

answer 8

Each column is one mask, from 0 up. The top row is the mask in binary, and the last digit is bit 0. This board replays Subsets (LeetCode 78), while the step debugger opens on Binary Watch (LeetCode 401), the other problem of this card.

  • mask the mask being read
  • subset the subset built from that mask

There are 3 items, so range(1 << 3) gives 8 masks, 0 to 7. Each mask is one subset.

Bit i of mask belongs to nums[i], counted from 0. Testing bit i + 1 would never choose nums[0].

mask = 0 is 000, so no bit is on. The subset is [].

mask = 1 is 001, so bit 0 is on. The subset is [1].

mask = 2 is 010, so bit 1 is on. The subset is [2].

mask = 3 is 011, so bits 0 and 1 are on. The subset is [1, 2].

mask = 4 is 100, so bit 2 is on. The subset is [3].

mask = 5 is 101, so bits 0 and 2 are on. The subset is [1, 3].

mask = 6 is 110, so bits 1 and 2 are on. The subset is [2, 3].

mask = 7 is 111, so bits 0 and 1 and 2 are on. The subset is [1, 2, 3].

The loop built 8 subsets, which is 2^3. No subset appears twice, because no two masks have the same bits.

mask
0000
0011
0102
0113
1004
1015
1106
1117
subset
1 / 11
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Leaving out the check hour < 12 and minute < 60

    Mistake
    times.append(f"{hour}:{minute:02d}")
    Fix
    if hour < 12 and minute < 60:
        times.append(f"{hour}:{minute:02d}")

    What happens: The hour field has 4 bits, so it can hold values up to 15. The minute field has 6 bits, so it can hold values up to 63. A watch shows only hours 0 to 11 and minutes 0 to 59. Without the check, masks such as 15:63 are added as times.

    Example: turnedOn = 10
    Mistake: returns ['15:63']Fix: returns []
  2. 2

    Gotcha 2: Writing the minute without :02d

    Mistake
    times.append(f"{hour}:{minute}")
    Fix
    times.append(f"{hour}:{minute:02d}")

    What happens: Binary Watch (LC 401) writes the minute with two digits, as in "0:01". f"{minute}" writes 1 as "1", so the time becomes "0:1". :02d pads the minute with a zero to two digits.

    Example: turnedOn = 1, the first two times
    Mistake: returns ['0:1', '0:2']Fix: returns ['0:01', '0:02']
  3. 3

    Gotcha 3: Testing bit i + 1 for item i

    Mistake
    result.append([nums[i] for i in range(n) if (mask >> (i + 1)) & 1])
    Fix
    result.append([nums[i] for i in range(n) if (mask >> i) & 1])

    What happens: Bit i of mask stands for nums[i], and bits are numbered from 0. (mask >> (i + 1)) & 1 reads the bit of the next item instead. Item 0 is then never chosen, and some subsets appear twice.

    Example: nums = [1, 2]
    Mistake: returns [[], [], [1], [1]]Fix: returns [[], [1], [2], [1, 2]]
  4. 4

    Gotcha 4: Forgetting the empty set after while sub > 0

    Mistake
    return submasks
    Fix
    submasks.append(0)
    return submasks

    What happens: The loop stops as soon as sub reaches 0. So the empty set, sub = 0, is never added inside the loop. submasks.append(0) adds it after the loop.

    Example: mask = 0b101
    Mistake: returns [5, 4, 1]Fix: returns [5, 4, 1, 0]
Not this one if…
  • The input has repeated values and each distinct subset may appear only once, as in Subsets II (LC 90): two different masks can build the same subset. See Subsets II
  • The best answer for one set of used items is needed again by many larger sets, as in Partition to K Equal Sum Subsets (LC 698): trying each subset on its own repeats that work. See Bitmask DP

Deep Dive

Why each integer is exactly one subset

An integer with n bits is a row of n on-or-off switches. Bit i of mask is on exactly when item i is in the subset. This is the card's invariant.

There are 2^n different bit patterns of length n, and range(1 << n) lists all of them once. There are also 2^n subsets of n items, because each item is either in or out. The two lists match one to one, so the loop visits every subset once and never repeats one.

In Subsets (LC 78) with nums = [1, 2, 3], the masks 0 to 7 build [], [1], [2], [1, 2], [3], [1, 3], [2, 3] and [1, 2, 3]. For example, mask = 5 is 0b101, so bits 0 and 2 are on, and the subset is [1, 3].

The three basic operations

To test whether item i is in mask, use (mask >> i) & 1. The shift >> i moves bit i to the lowest position. The & 1 keeps only that bit.

To count the items in mask, use bin(mask).count("1"). This count is called the popcount, the number of bits that are 1. read_binary_watch uses it to keep only the masks with turnedOn lit LEDs.

Bits are numbered from 0. Testing (mask >> (i + 1)) & 1 for item i reads the next item instead, so item 0 is never chosen.

Packed fields and sub-masks

A mask can hold several fields side by side. In Binary Watch (LC 401), the top 4 bits are the hour and the low 6 bits are the minute. mask >> 6 gives the hour, and mask & 0b111111 gives the minute.

Each field can hold more values than the problem allows. Four bits reach 15 and six bits reach 63, but a watch shows hours 0 to 11 and minutes 0 to 59. So read_binary_watch checks hour < 12 and minute < 60.

To visit only the subsets of one mask, enumerate_submasks steps with sub = (sub - 1) & mask. Subtracting 1 clears the lowest set bit and sets every bit below it. The & mask removes the bits that are not in mask. The next value is the next smaller sub-mask. For mask = 0b1101, the sequence is 13, 12, 9, 8, 5, 4, 1, and then 0.

The loop while sub > 0 stops before 0, so the empty sub-mask must be added after the loop.

The cost: 2^n masks times work per mask

subsets creates 2^N masks, and each one builds a list in O(N) time. The total is O(N * 2^N) time. The output holds up to N * 2^N numbers, so the space is also O(N * 2^N).

read_binary_watch has B = 10 bits. It visits 1024 masks and counts the bits of each one. This is why the card writes O(B * 2^B). The result is small: 10 times for one lit LED and 44 times for two.

enumerate_submasks returns 2^k values for a mask with k bits on. Summed over all masks of n bits, the total is 3^n. For n = 1 to 8, the totals are 3, 9, 27, 81, 243, 729, 2187 and 6561. Each item is in one of three states: outside mask, in mask but not in the sub-mask, or in both.

This is why the card's trigger says n is about 20 or less. With n = 20 there are 1,048,576 masks. Each extra item doubles that number.

60-Second Pitch

"With n up to about 20, I can try every subset. I represent a subset as an integer mask, where bit i is on when item i is chosen. The integers from 0 to 2^n - 1 match the subsets one to one. So one loop over range(1 << n) visits every subset once, with no recursion and no duplicates. To test an item, I use (mask >> i) & 1. To build the subset, I collect the items with a bit on. The cost is 2^n masks and O(n) work for each, so O(n * 2^n) time. If I only need to visit the subsets of one mask, I step with sub = (sub - 1) & mask, and I add the empty set after the loop."

Common Questions

Why does range(1 << n) visit every subset exactly once?

Each integer from 0 to 2^n - 1 has a different pattern of n bits. Each pattern says, for every item, whether it is in or out. There are 2^n such patterns and 2^n subsets, and each pattern maps to one subset. So no subset is repeated and none is missed.

How is this different from the recursive approach in Subsets (LC 78)?

Both produce all 2^N subsets in O(N * 2^N) time. The recursive Take or Skip approach builds a path and undoes each choice. The mask loop has no recursion and no undo step. The recursive approach is easier to extend with pruning or with a rule for duplicate values.

How do you iterate over the subsets of a given mask?

Start with sub = mask. Process sub, then set sub = (sub - 1) & mask. The subtraction clears the lowest set bit and sets the bits below it. The & mask removes bits that are outside mask. Stop when sub is 0, and handle the empty set separately.

What is the cost of visiting all sub-masks of all masks?

It is O(3^n). Each of the n items is in one of three states: outside the mask, in the mask but not in the sub-mask, or in both. For n = 8, the total is 6561.

When is a bitmask the wrong tool?

When n is above about 20, because the loop needs 2^n steps. It is also the wrong tool when the input has repeated values and each subset must appear once, because different masks then build the same subset. In both cases use another method, such as the Take or Skip card with a skip rule.

SWE Reasoning

Spot it

  • n is small, roughly 20 or less, and the task is to try every subset.
  • A DP state is which items are used so far, so the state itself can be one integer.
  • The problem has a few fixed on-or-off switches, such as the LEDs in Binary Watch (LC 401).
  • You need all subsets in a flat loop, with no recursion and no path list to undo.
  • The input has distinct items. If values repeat, as in Subsets II (LC 90), two masks can build the same subset.

The invariant, step by step

  1. mask takes each value from 0 to (1 << n) - 1, one value per loop step.
  2. Bit i of mask is on exactly when item i is in the subset.
  3. (mask >> i) & 1 reads bit i, and the list comprehension collects the nums[i] whose bit is on. This rebuilds the subset.
  4. Different masks differ in at least one bit, so they differ in at least one item. No subset is built twice.
  5. Every choice of in-or-out for the n items is one bit pattern, and that pattern is in the range. No subset is missed.
  6. For sub-masks, sub = (sub - 1) & mask always moves to the next smaller value whose bits are all inside mask. It starts at mask and ends after 0 is reached.

Traps

  • Skip the range check on a packed mask. Without hour < 12 and minute < 60, read_binary_watch(10) returns ['15:63'] instead of [].
  • Write the minute without :02d. The time becomes '0:1' instead of '0:01'.
  • Test bit i + 1 for item i. Item 0 is never chosen. For [1, 2], the result is [[], [], [1], [1]] instead of [[], [1], [2], [1, 2]].
  • Forget the empty sub-mask. The loop while sub > 0 never appends 0. For mask = 0b101, the result is [5, 4, 1] instead of [5, 4, 1, 0].
  • Use the card when n is large. The loop has 2^n steps. At n = 40 it cannot finish. Check the constraint on n before choosing it.
  • Duplicate values in the input. Two different masks can build the same subset. Subsets II (LC 90) needs sorting and a skip rule, as the Take or Skip card shows.

Practice

Canonical LeetCode:Subsets (LC 78)Binary Watch (LC 401)Count Number of Maximum Bitwise-OR Subsets (LC 2044)Number of Valid Words for Each Puzzle (LC 1178)Generalized Abbreviation (LC 320)Maximum Rows Covered by Columns (LC 2397)
Theory Context•Bit Manipulation

Builds on Subsets, which you haven’t finished yet.

P3MediumMental model~3 min speedrun

Bitmask Enumeration (Subset Masks)

Unlocks
Bitmask DP

Binary Watch (LC 401)(opens leetcode.com)

A binary watch shows the time with 10 LEDs. Four LEDs on the top encode the hour (0 to 11) and six LEDs on the bottom encode the minute (0 to 59); each LED is one bit, with the lowest bit on the right.

Given turnedOn, the number of LEDs that are lit, return every time the watch could be showing, in any order. Write the hour without a leading zero ("1:00", never "01:00") and the minute with exactly two digits ("10:02", never "10:2").

Example 1
Input:turnedOn = 1
Output:["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]
One lit LED is a single minute bit (1, 2, 4, 8, 16 or 32 minutes) or a single hour bit (1, 2, 4 or 8 o'clock).
8041221332416586472819hour LEDsminute LEDs

Bitmask Subsets

collection ➔ integer mask ➔ subset states
Spot it when: n is small (roughly 20 or less) and you must try every subset, or a DP state is 'which items are used so far'.
Bitmask Subsets
Python
1def read_binary_watch(turnedOn: int) -> list[str]:
2 """Binary Watch: every set of lit LEDs is one 10-bit mask.
3 Time: O(B * 2^B), B = 10 LEDs | Space: O(1) extra, output not counted
4 """
5 times: list[str] = [] # valid "h:mm" strings found so far
6 
7 # 1. Visit Every Possible Mask:
8 # Each integer 0 to 2^10 - 1 is one assignment of the 10 LEDs (4 hour bits,
9 # 6 minute bits), so looping over that range visits every combination once.
10 for mask in range(1 << 10):
11 if bin(mask).count("1") != turnedOn: # Popcount: how many LEDs are lit
12 continue
13 hour, minute = mask >> 6, mask & 0b111111 # Top 4 bits: hour; low 6 bits: minute
14 
15 # 2. Reject Out-Of-Range Fields:
16 # 4 bits can hold values up to 15 and 6 bits up to 63, but a watch only
17 # shows hours 0-11 and minutes 0-59, so a decoded field outside that range is dropped.
18 if hour < 12 and minute < 60:
19 times.append(f"{hour}:{minute:02d}")
20 return times
21 
22 
23def subsets(nums: list[int]) -> list[list[int]]:
24 """Subsets: every subset of distinct nums, one bitmask per subset.
25 Time: O(N * 2^N) | Space: O(N * 2^N) output
26 """
27 n = len(nums)
28 result: list[list[int]] = [] # one subset per mask
29 
30 # 1. Decode Each Mask Into Its Subset:
31 # Each integer 0 to 2^n - 1 is one subset; bit i of mask tells whether
32 # nums[i] is included, so collecting the items whose bit is on rebuilds that subset.
33 for mask in range(1 << n):
34 result.append([nums[i] for i in range(n) if (mask >> i) & 1])
35 return result
36 
37 
38def enumerate_submasks(mask: int) -> list[int]:
39 """Sub-Mask Enumeration: every subset of the bits set in `mask`, largest first.
40 Time: O(2^k), k = popcount(mask); O(3^n) summed over all masks | Space: O(2^k) output
41 """
42 submasks: list[int] = [] # every non-empty submask found so far, largest first
43 sub = mask
44 
45 # 1. Step Through Every Non-Empty Submask:
46 # (sub - 1) & mask clears the lowest set bit of sub and sets every bit below
47 # it, then masking with mask drops anything outside it, landing on the next smaller submask.
48 while sub > 0:
49 submasks.append(sub)
50 sub = (sub - 1) & mask # Drop the lowest bit, keep only bits inside mask
51 
52 # 2. Add the Empty Set:
53 # The loop above stops once sub reaches 0, so the empty submask is never
54 # appended inside it; add it once here.
55 submasks.append(0)
56 return submasks
Invariant: Bit i of mask is on exactly when item i is in the subset, so each number in range(1 << n) is one subset and every subset appears once.

An integer is a set: bit i is on when item i is chosen, so the subsets of n items are exactly the numbers in range(1 << n), (mask >> i) & 1 says whether item i is in, and bin(mask).count("1") how many items are. Each subset matches one number and each number one subset, so one loop visits every subset exactly once, with no recursion and no duplicates; to visit only the subsets of one mask, step with sub = (sub - 1) & mask, which clears the lowest set bit, sets the bits below it, and keeps only bits of mask. The trap is decoding a packed mask: split it into its fields (hour = mask >> 6, minute = mask & 0b111111) and check each field's range (hour < 12 and minute < 60), because 4 bits reach 15 and 6 bits reach 63.

See it run

Subsets of nums = [1, 2, 3] with one mask per subset

n 3 · masks 8

test (mask >> i) & 1

mask 0 (000) · subset []

mask 1 (001) · subset [1]

mask 2 (010) · subset [2]

mask 3 (011) · subset [1, 2]

mask 4 (100) · subset [3]

mask 5 (101) · subset [1, 3]

mask 6 (110) · subset [2, 3]

mask 7 (111) · subset [1, 2, 3]

answer 8

Each column is one mask, from 0 up. The top row is the mask in binary, and the last digit is bit 0. This board replays Subsets (LeetCode 78), while the step debugger opens on Binary Watch (LeetCode 401), the other problem of this card.

  • mask the mask being read
  • subset the subset built from that mask

There are 3 items, so range(1 << 3) gives 8 masks, 0 to 7. Each mask is one subset.

Bit i of mask belongs to nums[i], counted from 0. Testing bit i + 1 would never choose nums[0].

mask = 0 is 000, so no bit is on. The subset is [].

mask = 1 is 001, so bit 0 is on. The subset is [1].

mask = 2 is 010, so bit 1 is on. The subset is [2].

mask = 3 is 011, so bits 0 and 1 are on. The subset is [1, 2].

mask = 4 is 100, so bit 2 is on. The subset is [3].

mask = 5 is 101, so bits 0 and 2 are on. The subset is [1, 3].

mask = 6 is 110, so bits 1 and 2 are on. The subset is [2, 3].

mask = 7 is 111, so bits 0 and 1 and 2 are on. The subset is [1, 2, 3].

The loop built 8 subsets, which is 2^3. No subset appears twice, because no two masks have the same bits.

mask
0000
0011
0102
0113
1004
1015
1106
1117
subset
1 / 11
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Leaving out the check hour < 12 and minute < 60

    Mistake
    times.append(f"{hour}:{minute:02d}")
    Fix
    if hour < 12 and minute < 60:
        times.append(f"{hour}:{minute:02d}")

    What happens: The hour field has 4 bits, so it can hold values up to 15. The minute field has 6 bits, so it can hold values up to 63. A watch shows only hours 0 to 11 and minutes 0 to 59. Without the check, masks such as 15:63 are added as times.

    Example: turnedOn = 10
    Mistake: returns ['15:63']Fix: returns []
  2. 2

    Gotcha 2: Writing the minute without :02d

    Mistake
    times.append(f"{hour}:{minute}")
    Fix
    times.append(f"{hour}:{minute:02d}")

    What happens: Binary Watch (LC 401) writes the minute with two digits, as in "0:01". f"{minute}" writes 1 as "1", so the time becomes "0:1". :02d pads the minute with a zero to two digits.

    Example: turnedOn = 1, the first two times
    Mistake: returns ['0:1', '0:2']Fix: returns ['0:01', '0:02']
  3. 3

    Gotcha 3: Testing bit i + 1 for item i

    Mistake
    result.append([nums[i] for i in range(n) if (mask >> (i + 1)) & 1])
    Fix
    result.append([nums[i] for i in range(n) if (mask >> i) & 1])

    What happens: Bit i of mask stands for nums[i], and bits are numbered from 0. (mask >> (i + 1)) & 1 reads the bit of the next item instead. Item 0 is then never chosen, and some subsets appear twice.

    Example: nums = [1, 2]
    Mistake: returns [[], [], [1], [1]]Fix: returns [[], [1], [2], [1, 2]]
  4. 4

    Gotcha 4: Forgetting the empty set after while sub > 0

    Mistake
    return submasks
    Fix
    submasks.append(0)
    return submasks

    What happens: The loop stops as soon as sub reaches 0. So the empty set, sub = 0, is never added inside the loop. submasks.append(0) adds it after the loop.

    Example: mask = 0b101
    Mistake: returns [5, 4, 1]Fix: returns [5, 4, 1, 0]
Not this one if…
  • The input has repeated values and each distinct subset may appear only once, as in Subsets II (LC 90): two different masks can build the same subset. See Subsets II
  • The best answer for one set of used items is needed again by many larger sets, as in Partition to K Equal Sum Subsets (LC 698): trying each subset on its own repeats that work. See Bitmask DP

Deep Dive

Why each integer is exactly one subset

An integer with n bits is a row of n on-or-off switches. Bit i of mask is on exactly when item i is in the subset. This is the card's invariant.

There are 2^n different bit patterns of length n, and range(1 << n) lists all of them once. There are also 2^n subsets of n items, because each item is either in or out. The two lists match one to one, so the loop visits every subset once and never repeats one.

In Subsets (LC 78) with nums = [1, 2, 3], the masks 0 to 7 build [], [1], [2], [1, 2], [3], [1, 3], [2, 3] and [1, 2, 3]. For example, mask = 5 is 0b101, so bits 0 and 2 are on, and the subset is [1, 3].

The three basic operations

To test whether item i is in mask, use (mask >> i) & 1. The shift >> i moves bit i to the lowest position. The & 1 keeps only that bit.

To count the items in mask, use bin(mask).count("1"). This count is called the popcount, the number of bits that are 1. read_binary_watch uses it to keep only the masks with turnedOn lit LEDs.

Bits are numbered from 0. Testing (mask >> (i + 1)) & 1 for item i reads the next item instead, so item 0 is never chosen.

Packed fields and sub-masks

A mask can hold several fields side by side. In Binary Watch (LC 401), the top 4 bits are the hour and the low 6 bits are the minute. mask >> 6 gives the hour, and mask & 0b111111 gives the minute.

Each field can hold more values than the problem allows. Four bits reach 15 and six bits reach 63, but a watch shows hours 0 to 11 and minutes 0 to 59. So read_binary_watch checks hour < 12 and minute < 60.

To visit only the subsets of one mask, enumerate_submasks steps with sub = (sub - 1) & mask. Subtracting 1 clears the lowest set bit and sets every bit below it. The & mask removes the bits that are not in mask. The next value is the next smaller sub-mask. For mask = 0b1101, the sequence is 13, 12, 9, 8, 5, 4, 1, and then 0.

The loop while sub > 0 stops before 0, so the empty sub-mask must be added after the loop.

The cost: 2^n masks times work per mask

subsets creates 2^N masks, and each one builds a list in O(N) time. The total is O(N * 2^N) time. The output holds up to N * 2^N numbers, so the space is also O(N * 2^N).

read_binary_watch has B = 10 bits. It visits 1024 masks and counts the bits of each one. This is why the card writes O(B * 2^B). The result is small: 10 times for one lit LED and 44 times for two.

enumerate_submasks returns 2^k values for a mask with k bits on. Summed over all masks of n bits, the total is 3^n. For n = 1 to 8, the totals are 3, 9, 27, 81, 243, 729, 2187 and 6561. Each item is in one of three states: outside mask, in mask but not in the sub-mask, or in both.

This is why the card's trigger says n is about 20 or less. With n = 20 there are 1,048,576 masks. Each extra item doubles that number.

60-Second Pitch

"With n up to about 20, I can try every subset. I represent a subset as an integer mask, where bit i is on when item i is chosen. The integers from 0 to 2^n - 1 match the subsets one to one. So one loop over range(1 << n) visits every subset once, with no recursion and no duplicates. To test an item, I use (mask >> i) & 1. To build the subset, I collect the items with a bit on. The cost is 2^n masks and O(n) work for each, so O(n * 2^n) time. If I only need to visit the subsets of one mask, I step with sub = (sub - 1) & mask, and I add the empty set after the loop."

Common Questions

Why does range(1 << n) visit every subset exactly once?

Each integer from 0 to 2^n - 1 has a different pattern of n bits. Each pattern says, for every item, whether it is in or out. There are 2^n such patterns and 2^n subsets, and each pattern maps to one subset. So no subset is repeated and none is missed.

How is this different from the recursive approach in Subsets (LC 78)?

Both produce all 2^N subsets in O(N * 2^N) time. The recursive Take or Skip approach builds a path and undoes each choice. The mask loop has no recursion and no undo step. The recursive approach is easier to extend with pruning or with a rule for duplicate values.

How do you iterate over the subsets of a given mask?

Start with sub = mask. Process sub, then set sub = (sub - 1) & mask. The subtraction clears the lowest set bit and sets the bits below it. The & mask removes bits that are outside mask. Stop when sub is 0, and handle the empty set separately.

What is the cost of visiting all sub-masks of all masks?

It is O(3^n). Each of the n items is in one of three states: outside the mask, in the mask but not in the sub-mask, or in both. For n = 8, the total is 6561.

When is a bitmask the wrong tool?

When n is above about 20, because the loop needs 2^n steps. It is also the wrong tool when the input has repeated values and each subset must appear once, because different masks then build the same subset. In both cases use another method, such as the Take or Skip card with a skip rule.

SWE Reasoning

Spot it

  • n is small, roughly 20 or less, and the task is to try every subset.
  • A DP state is which items are used so far, so the state itself can be one integer.
  • The problem has a few fixed on-or-off switches, such as the LEDs in Binary Watch (LC 401).
  • You need all subsets in a flat loop, with no recursion and no path list to undo.
  • The input has distinct items. If values repeat, as in Subsets II (LC 90), two masks can build the same subset.

The invariant, step by step

  1. mask takes each value from 0 to (1 << n) - 1, one value per loop step.
  2. Bit i of mask is on exactly when item i is in the subset.
  3. (mask >> i) & 1 reads bit i, and the list comprehension collects the nums[i] whose bit is on. This rebuilds the subset.
  4. Different masks differ in at least one bit, so they differ in at least one item. No subset is built twice.
  5. Every choice of in-or-out for the n items is one bit pattern, and that pattern is in the range. No subset is missed.
  6. For sub-masks, sub = (sub - 1) & mask always moves to the next smaller value whose bits are all inside mask. It starts at mask and ends after 0 is reached.

Traps

  • Skip the range check on a packed mask. Without hour < 12 and minute < 60, read_binary_watch(10) returns ['15:63'] instead of [].
  • Write the minute without :02d. The time becomes '0:1' instead of '0:01'.
  • Test bit i + 1 for item i. Item 0 is never chosen. For [1, 2], the result is [[], [], [1], [1]] instead of [[], [1], [2], [1, 2]].
  • Forget the empty sub-mask. The loop while sub > 0 never appends 0. For mask = 0b101, the result is [5, 4, 1] instead of [5, 4, 1, 0].
  • Use the card when n is large. The loop has 2^n steps. At n = 40 it cannot finish. Check the constraint on n before choosing it.
  • Duplicate values in the input. Two different masks can build the same subset. Subsets II (LC 90) needs sorting and a skip rule, as the Take or Skip card shows.

Practice

Canonical LeetCode:Subsets (LC 78)Binary Watch (LC 401)Count Number of Maximum Bitwise-OR Subsets (LC 2044)Number of Valid Words for Each Puzzle (LC 1178)Generalized Abbreviation (LC 320)Maximum Rows Covered by Columns (LC 2397)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•