Subsets Generation Using Bitmask in DSA C# - Time & Space Complexity
We want to understand how the time needed to generate all subsets grows as the input size increases.
How does the number of operations change when we create subsets using bitmasks?
Analyze the time complexity of the following code snippet.
int n = nums.Length;
for (int mask = 0; mask < (1 << n); mask++) {
List<int> subset = new List<int>();
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) {
subset.Add(nums[i]);
}
}
// process subset
}
This code generates all subsets of an array by using bitmasks to decide which elements to include.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The outer loop runs through all possible bitmasks (subsets), and the inner loop checks each element to see if it is included.
- How many times: Outer loop runs 2^n times, inner loop runs n times for each outer iteration.
As the input size n grows, the number of subsets doubles for each new element, and for each subset, we check n elements.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 * 2^10 = 10,240 operations |
| 20 | About 20 * 2^20 = 20,971,520 operations |
| 30 | About 30 * 2^30 = 32,212,254,720 operations |
Pattern observation: The operations grow very fast, doubling with each added element and multiplying by n.
Time Complexity: O(n * 2^n)
This means the time needed grows very fast as the input size increases, because we generate all subsets and check each element for every subset.
[X] Wrong: "Generating subsets using bitmask is just O(2^n) because there are 2^n subsets."
[OK] Correct: We also check each element for every subset, so the inner loop adds a factor of n, making it O(n * 2^n).
Understanding this time complexity helps you explain why generating all subsets is expensive and shows your grasp of exponential growth in algorithms.
"What if we used recursion instead of bitmask loops? How would the time complexity change?"
Practice
Solution
Step 1: Bit assignments
Bit 0 (rightmost) → element 0 (first). Bit 1 → element 1. Bit 2 → element 2 (third).Step 2: Subset {element 0, element 2}
Bit 0 = 1, bit 1 = 0, bit 2 = 1. Binary: 101. Decimal: 4+0+1 = 5.Final Answer:
5 (binary 101) -> Option BQuick Check:
First element = bit 0, third = bit 2. 101 in binary = 5. [OK]
- Using bit 1=first instead of bit 0=first
- Getting 6 (binary 110 = second+third)
- Getting 3 (binary 011 = first+second)
Solution
Step 1: AND isolates a bit
(mask & (1 << j)) keeps only bit j from mask. If bit j was set, result is non-zero (true). If not set, result is 0 (false).Step 2: Why others fail
OR always sets bit j → always non-zero → always true. XOR flips bit j → non-zero whether set or not. >> j == 1 fails if higher bits are also set.Final Answer:
(mask & (1 << j)) != 0 -> Option CQuick Check:
AND isolates. OR sets. XOR flips. Use & to check a bit. [OK]
- Using | which always returns non-zero
- Using >> j == 1 which fails with higher set bits
- Using ^ which gives non-zero for both set and unset bits
Solution
Step 1: Decode binary 101
Bit 0 = 1 → include nums[0] = 1. Bit 1 = 0 → exclude nums[1] = 2. Bit 2 = 1 → include nums[2] = 3.Step 2: Result
Subset = {1, 3}.Final Answer:
{1, 3} -> Option CQuick Check:
101: bits 0 and 2 set → nums[0]=1 and nums[2]=3 → {1,3}. [OK]
- Reading bits left-to-right and getting {3,1}
- Getting {2,3} by reading bit positions wrong
- Including all elements because mask≠0
Solution
Step 1: Range needed
With n elements, bitmasks 0 through 2^n-1 each represent a unique subset. Total: 2^n masks.Step 2: Loop bounds
Start at 0 (mask=0 is the empty set - important to include). End at (1<Step 3: Why not start at 1?
Starting at 1 skips mask=0 which represents the empty subset.Final Answer:
mask from 0 to (1< -> Option BQuick Check:
mask=0 = empty set. mask=(1<
- Starting at 1 and missing the empty subset
- Using <= (1<
- Looping to n instead of 2^n
Solution
Step 1: Outer loop
2^n iterations (one per mask).Step 2: Inner loop
For each mask, we check n bits to determine which elements are included → O(n) per mask.Step 3: Total
2^n masks × O(n) per mask = O(n × 2^n).Final Answer:
O(n × 2^n) -> Option CQuick Check:
2^n subsets × n work each = O(n × 2^n). [OK]
- Saying O(2^n) and forgetting the n factor for the inner bit-check loop
- Confusing with O(n!) which is for permutations
- Saying O(n²) which is far too fast
Ready to prove it?
Pass the test and this pattern moves unknown
