Week 19: Bit Manipulation & Math
1. Overviewβ
Welcome to Week 19! After spending months building massive abstract data structures, we are zooming all the way in to the raw metal of the CPU: Bits and Bytes.
Bit manipulation performs operations directly on the binary representation of numbers. These algorithms execute in single CPU cycles β no heap allocations, no pointer chasing, no cache misses. A bit trick that replaces a HashSet lookup doesn't just reduce asymptotic complexity; it reduces constant factors by orders of magnitude.
Why Does This Matter?β
Bit manipulation appears in:
- Cryptography β AES, SHA, and every modern hash function rely on XOR extensively
- Network protocols β TCP/IP checksums, subnet masks, and routing tables use bit masking
- Game AI and bitmask DP β chess engines represent board states as 64-bit integers (bitboards)
- Embedded systems β reading sensor registers, controlling GPIO pins, all done with bit operations
- Interview "wow" moments β an
O(1)bit trick instead of anO(N)scan signals deep CS fundamentals
Goals for this week:
- Understand Java's 32-bit two's complement integer representation at a binary level.
- Master the difference between Signed (
>>) and Unsigned (>>>) right shifts. - Internalize the three magical XOR properties that solve "find the odd one out" problems.
- Use Bit Masks to track state and generate subsets for small-N exhaustive problems.
- Review core Math patterns: Fast Exponentiation, GCD, Sieve of Eratosthenes.
Knowledge You Need Before Startingβ
- Binary number basics and place-value conversion comfort.
- Integer overflow/signedness intuition in Java.
- Basic algebra and modular arithmetic foundations.
- Ability to connect bitmasks to subset/state representations.
2. The Core Mental Modelsβ
2.1 Two's Complement β Java's Integer Representationβ
Java uses 32-bit two's complement for int. Every number is stored as 32 bits:
Positive 5: 00000000 00000000 00000000 00000101
Negative 5: 11111111 11111111 11111111 11111011
To negate a number: flip all bits, then add 1.
5 = 00000101
~5 = 11111010 β flip all bits
-5 = 11111011 β add 1
Key property: x + (~x) = -1 (all 1s = -1 in two's complement)
~x = -x - 1 (so ~5 = -6)
Integer.MAX_VALUE = 2^31 - 1 = 01111111 11111111 11111111 11111111
Integer.MIN_VALUE = -2^31 = 10000000 00000000 00000000 00000000
Overflow: Integer.MIN_VALUE * -1 == Integer.MIN_VALUE (not MAX_VALUE + 1!)
Why does this matter for interviews? Problems like "Divide Two Integers" and "Sum of Two Integers" break in subtle ways when you forget about overflow at the boundaries of int range. Always test with Integer.MIN_VALUE.
2.2 The Shift Operators β <<, >>, >>>β
Three shift operators, each with a distinct behavior on the sign bit:
Number: -8 in binary (32 bits):
11111111 11111111 11111111 11111000
Left Shift (<<):
-8 << 1 β 11111111 11111111 11111110000 β -16
Effect: multiply by 2 (may overflow for large positives)
New bits filled from RIGHT: always 0s
Signed Right Shift (>>):
-8 >> 1 β 11111111 11111111 11111111 11111100 β -4
Effect: divide by 2 (rounds toward -β, not 0)
New bits filled from LEFT: copies the SIGN BIT (1 for negatives)
β -8 >> 1 = -4 β
, but -7 >> 1 = -4 (not -3!) β rounds down
Unsigned Right Shift (>>>):
-8 >>> 1 β 01111111 11111111 11111111 11111100 β 2147483644
Effect: divide by 2 treating as UNSIGNED (always positive result)
New bits filled from LEFT: always 0s
β Turns any negative int into a large positive number
When to use >>>?
- Counting bits of a value that might be negative:
while (n != 0) { count += n & 1; n >>>= 1; } - Computing the midpoint safely:
int mid = (left + right) >>> 1(avoids overflow, same asleft + (right-left)/2) - Any context where you want to treat an
intas 32 unsigned bits
2.3 The Magic of XOR β Three Properties That Solve Problemsβ
XOR (^) is the most powerful bit operation for interview problems. Internalize these three properties:
Property 1: X ^ X = 0 (self-cancellation)
Property 2: X ^ 0 = X (identity)
Property 3: Associativity and Commutativity
A ^ B ^ A = A ^ A ^ B = 0 ^ B = B
Proof of "Single Number" using these properties:
[4, 1, 2, 1, 2]
XOR all: 4 ^ 1 ^ 2 ^ 1 ^ 2
= 4 ^ (1^1) ^ (2^2) β rearrange (commutative)
= 4 ^ 0 ^ 0 β property 1
= 4 β property 2 β
Visual walkthrough:
Think of XOR as a "toggle light switch":
0 ^ 1 = 1 (off β on)
1 ^ 1 = 0 (on β off, back to off)
If every number appears twice, every switch gets toggled twice β all off (0).
The number that appears once: its switch gets toggled once β stays on.
Only that number remains in the result.
2.4 Brian Kernighan's Trick β "Drop the Lowest Set Bit"β
The expression n & (n - 1) removes the lowest 1 bit from n:
Why does n & (n-1) drop the lowest set bit?
n = ...1 0110 1000 (lowest 1 bit is at position 3)
n-1 = ...1 0110 0111 (subtracting 1 flips bit 3 and all below it)
(all bits ABOVE bit 3 are unchanged)
n & (n-1):
n = ...1 0110 1000
n-1 = ...1 0110 0111
AND = ...1 0110 0000 β bit 3 and all below are cleared!
Each iteration of while(n != 0) { n = n & (n-1); count++; }
drops exactly one '1' bit β loop runs exactly popcount(n) times.
For a 32-bit int with k set bits: k iterations instead of 32.
Derived tricks:
// Is n a power of 2? Powers of 2 have exactly one '1' bit.
boolean isPowerOfTwo = (n > 0) && (n & (n - 1)) == 0;
// n=8 (1000): 8 & 7 = 1000 & 0111 = 0000 β
// n=6 (0110): 6 & 5 = 0110 & 0101 = 0100 β 0 β
// Extract the lowest set bit (isolate it):
int lowestBit = n & (-n); // or equivalently: n & (~n + 1)
// n=12 (1100): -n = ...10100, n & -n = 0100 (just bit 2) β
// Check if bit i is set:
boolean isSet = (n & (1 << i)) != 0;
// Set bit i:
n = n | (1 << i);
// Clear bit i:
n = n & ~(1 << i);
// Toggle bit i:
n = n ^ (1 << i);
2.5 Bit Masking for Subset Enumerationβ
When a problem has N \leq 20 items and asks you to check all subsets, a bitmask loop replaces recursive backtracking with a clean integer loop:
nums = [1, 2, 3] (N=3, so 2^3 = 8 subsets)
mask = 000 (binary) β subset {}
mask = 001 β subset {nums[0]} = {1}
mask = 010 β subset {nums[1]} = {2}
mask = 011 β subset {nums[0], nums[1]} = {1, 2}
mask = 100 β subset {nums[2]} = {3}
mask = 101 β subset {nums[0], nums[2]} = {1, 3}
mask = 110 β subset {nums[1], nums[2]} = {2, 3}
mask = 111 β subset {nums[0], nums[1], nums[2]} = {1, 2, 3}
For each mask, bit i being set means nums[i] is in the subset.
Check: (mask >> i) & 1 == 1 OR (mask & (1 << i)) != 0
This replaces O(2^N Γ N) recursive backtracking with a simple double loop β same complexity, but no recursion stack overhead.
3. Theory & Fundamentalsβ
3.1 All Bitwise Operators β Quick Referenceβ
| Operator | Symbol | Effect | Interview Use |
|---|---|---|---|
| AND | & | 1 only if both 1 | Check/clear specific bits |
| OR | | | 1 if either 1 | Set specific bits |
| XOR | ^ | 1 if different | Toggle, cancel pairs, find unique |
| NOT | ~ | Flips all bits | ~x = -x - 1 in Java |
| Left Shift | << | Multiply by 2^k | Generate masks: 1 << i |
| Signed Right Shift | >> | Divide by 2^k (sign preserved) | Arithmetic division |
| Unsigned Right Shift | >>> | Divide by 2^k (zero-fill) | Treat int as unsigned |
3.2 Core Math Algorithmsβ
Fast Exponentiation (Binary Exponentiation):
Compute x^n in O(log n) instead of O(n):
Idea: x^8 = x^4 Γ x^4
x^4 = x^2 Γ x^2
x^2 = x Γ x
Binary decomposition of n:
x^13 = x^(1101 in binary)
= x^8 Γ x^4 Γ x^1 (from the set bits)
Implementation: square x each iteration; multiply into result when bit is set.
public double fastPow(double x, long n) {
if (n < 0) { x = 1 / x; n = -n; }
double result = 1.0;
while (n > 0) {
if ((n & 1) == 1) result *= x; // Current bit is set β multiply
x *= x; // Square the base
n >>= 1; // Move to next bit
}
return result;
}
GCD β Euclidean Algorithm:
gcd(a, b) = gcd(b, a % b) until b == 0
gcd(48, 18) = gcd(18, 12) = gcd(12, 6) = gcd(6, 0) = 6
Why? If d divides both a and b, it also divides (a % b).
Time: O(log(min(a, b)))
Sieve of Eratosthenes (Count Primes up to N):
Mark all composites by iterating through each prime's multiples:
N = 30:
Start with all true: [T,T,T,T,T,T,T,T,T,T,T,T...]
p=2: mark 4,6,8,10,12...30 as false
p=3: mark 9,15,21,27 as false (6,12... already marked)
p=5: mark 25 as false
p>=6: no new composites (6 > sqrt(30)β5.47)
Remaining trues: 2,3,5,7,11,13,17,19,23,29 β 10 primes β
Key optimization: start marking from pΒ² (not 2p), since smaller multiples
already marked. Outer loop only goes to sqrt(N).
Time: O(N log log N) Space: O(N)
3.3 The ones and twos State Machine (Single Number II Optimization)β
For the "every element appears 3 times, find the one that appears once" problem, the hardware-level optimization tracks bits using a two-variable state machine:
State machine for each bit position:
0 appearances β state (ones=0, twos=0)
1 appearance β state (ones=1, twos=0)
2 appearances β state (ones=0, twos=1)
3 appearances β state (ones=0, twos=0) β reset, same as 0
Transition logic:
ones = (ones ^ num) & ~twos
twos = (twos ^ num) & ~ones
Why this works:
When a bit appears for the 1st time: ones flips to 1, twos stays 0
When it appears for the 2nd time: ones flips back to 0, twos flips to 1
When it appears for the 3rd time: twos flips back to 0, ones stays 0
β Back to (0, 0). The bit effectively "doesn't count" after 3 appearances.
β The unique number (appears once) remains in `ones`.
This processes ALL 32 bits simultaneously in a single pass β true hardware-level parallelism.
4. Code Templates (Java)β
Template 1: Brian Kernighan's β Count Set Bitsβ
public int countBits(int n) {
int count = 0;
while (n != 0) {
n = n & (n - 1); // Drop the lowest 1-bit
count++;
}
return count;
}
// Runs exactly popcount(n) iterations β much faster than checking all 32 bits
// when the number is sparse (few 1-bits).
Template 2: Hamming Weight with Unsigned Shiftβ
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
count += (n & 1); // Is the rightmost bit 1?
n >>>= 1; // Unsigned shift: always fills with 0s on left
}
return count;
}
// Must use >>> not >>: negative ints with >> would shift in 1s forever,
// creating an infinite loop (n never becomes 0).
Template 3: XOR β Find the Unique Elementβ
public int singleNumber(int[] nums) {
int result = 0;
for (int num : nums) {
result ^= num; // Duplicates cancel: X ^ X = 0; unique survives: X ^ 0 = X
}
return result;
}
Template 4: Bitmask Subset Generationβ
public List<List<Integer>> subsets(int[] nums) {
int n = nums.length;
int total = 1 << n; // 2^n subsets
List<List<Integer>> result = new ArrayList<>();
for (int mask = 0; mask < total; mask++) {
List<Integer> subset = new ArrayList<>();
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) { // Is bit i set in mask?
subset.add(nums[i]);
}
}
result.add(subset);
}
return result;
}
Template 5: Fast Exponentiationβ
public double myPow(double x, int n) {
long N = n; // Use long to handle Integer.MIN_VALUE negation safely
if (N < 0) { x = 1.0 / x; N = -N; }
double result = 1.0;
while (N > 0) {
if ((N & 1) == 1) result *= x; // Odd: multiply current x into result
x *= x; // Square x for next bit
N >>= 1; // Move to next bit
}
return result;
}
// n = Integer.MIN_VALUE: int negation overflows! Long handles it safely.
Template 6: Single Number II (State Machine)β
public int singleNumber(int[] nums) {
int ones = 0, twos = 0;
for (int num : nums) {
ones = (ones ^ num) & ~twos;
twos = (twos ^ num) & ~ones;
}
return ones; // Contains bits that appeared exactly once (mod 3)
}
5. Pattern Recognition Guideβ
5.1 The Decision Flowchartβ
START
β
Does the problem involve finding
a unique/missing/duplicate number
with O(1) space constraint?
β
ββ YES β΄β NO βββββββββββββββββββββββββββββββββββ
β β
βΌ βΌ
Every element appears N times Is this a "check all subsets"
except one which appears M times? problem with N β€ 20?
β β
ββ YES β΄β NO ββββββββββββββ ββ YES β΄β NO
β β β
βΌ βΌ βΌ
N=2, M=1: N=3, M=1: Bitmask loop
XOR all Bit count (Template 4)
(Template 3) mod 3, or
state machine
(Template 6)
β
Does the problem involve:
ββββββββββββ¬βββββββββββββββ¬ββββββββββββββββ
β β β β
βΌ βΌ βΌ βΌ
"Power of 2" "Count 1s" "Exponentiation" "Count primes"
n & (n-1)==0 Kernighan Fast Pow Sieve of
(Template 1) (Template 5) Eratosthenes
5.2 Keyword Trigger Tableβ
| Problem Keywords | Technique | Key Operation |
|---|---|---|
| "find the single/missing number" + O(1) space | XOR | result ^= num |
| "every element appears twice except one" | XOR | All duplicates cancel |
| "every element appears 3 times except one" | Bit-count mod 3 or state machine | ones/twos state machine |
| "number of 1 bits" / "Hamming weight" | Brian Kernighan | n = n & (n-1) |
| "Hamming distance" between x and y | XOR + count bits | countBits(x ^ y) |
| "is N a power of 2?" | Bit trick | n > 0 && (n & (n-1)) == 0 |
| "power of x, n" / "fast exponentiation" | Binary exponentiation | Square and multiply |
| "generate all subsets" with N β€ 20 | Bitmask enumeration | for mask in 0..2^N |
| "count primes up to N" | Sieve of Eratosthenes | Mark composites, count trues |
| "GCD / LCM" | Euclidean algorithm | gcd(a,b) = gcd(b, a%b) |
"divide without /, *, %" | Bit shifting | Simulate with <<, >> |
"add without +" | XOR + AND for carry | a^b = sum bits, (a&b)<<1 = carry |
| "reverse bits" | Loop with >>> and |= | Extract rightmost, insert leftmost |
| "find two non-repeating numbers" | XOR + partition by rightmost set bit | x & -x to find separator |
5.3 Common Traps & How to Avoid Themβ
Trap 1: Using >> instead of >>> for unsigned bit counting
// β Infinite loop for negative numbers!
// -1 in binary: all 1s. -1 >> 1 = -1 (sign bit preserved). Loop never ends.
while (n != 0) { count += (n & 1); n >>= 1; }
// β
Always use >>> for bit-counting to treat int as 32 unsigned bits
while (n != 0) { count += (n & 1); n >>>= 1; }
Trap 2: Integer overflow when negating Integer.MIN_VALUE
// Integer.MIN_VALUE = -2^31 = -2147483648
// Its negation would be 2^31 = 2147483648, which OVERFLOWS int!
int n = Integer.MIN_VALUE;
int pos = -n; // Still -2147483648! Overflow.
// β
Cast to long before negating
long N = n;
if (N < 0) N = -N; // Safe: long can hold 2^31
Trap 3: ~ is NOT the same as - (bitwise NOT vs. arithmetic negation)
// In Java: ~x = -x - 1 (NOT the same as -x)
// ~5 = -6 (not -5!)
// ~(-1) = 0 (not 1!)
// β Confusing ~ with negation:
int complement = ~x; // This is -x-1, not -x
// β
To negate: use unary minus
int negX = -x;
// Use ~ correctly: to clear a bit at position i:
n = n & ~(1 << i); // ~(1 << i) = all 1s except bit i = correct mask
Trap 4: Bit shift on negative exponent without converting to long
// Pow(x, n) where n = Integer.MIN_VALUE:
// β int negation overflows
int absN = -n; // Still Integer.MIN_VALUE!
// β
Store in long first
long N = n; // Now N = -2147483648 as a long (fits)
if (N < 0) { x = 1/x; N = -N; } // N = 2147483648, fits in long
Trap 5: Off-by-one in Sieve of Eratosthenes
// Count primes LESS THAN n (not less than or equal to):
// β Wrong: includes n itself
boolean[] isPrime = new boolean[n];
// ... count all trues β may count n if n is prime
// β
Correct: primes strictly less than n
// boolean[n] has indices 0..n-1, so isPrime[n-1] is the last check β
// Start sieve from p=2, and mark composites starting from p*p (not 2*p)
for (int p = 2; (long)p * p < n; p++) { // Use long to avoid p*p overflow
if (isPrime[p]) {
for (int j = p * p; j < n; j += p) {
isPrime[j] = false;
}
}
}
Trap 6: Applying (mask & (1 << i)) != 0 when i β₯ 31 causes overflow
// 1 << 31 overflows int (becomes Integer.MIN_VALUE = negative!)
// 1 << 32 = 1 (wraps around in Java!)
// β Works for i=0..30, breaks for i=31
if ((mask & (1 << i)) != 0) { ... } // When i=31: 1<<31 = MIN_VALUE
// β
Use 1L for long bit shifts when i can be 31 or larger
if ((mask & (1L << i)) != 0) { ... } // 1L<<31 = 2147483648 (correct)
Trap 7: Forgetting that ^= is XOR-equals, not exponentiation
// Java has no exponentiation operator. ^ is XOR, not power!
// β Confusing with Python where ** is exponentiation
int result = x ^ n; // This is XOR, not x to the power n!
// β
For exponentiation: use Math.pow() or fast exponentiation template
double power = Math.pow(x, n);
int power = fastPow(x, n); // Custom template for integer power
6. Worked Examples (Step-by-Step Walkthroughs)β
Example 1: LeetCode 136 β Single Numberβ
Problem: Every element appears twice except one. Find it in O(N) time and O(1) space.
Thought process:
O(1)space rules out HashSets and sorted arrays.- XOR property: pairs cancel (
X ^ X = 0), unique survives (X ^ 0 = X). - XOR all numbers β order doesn't matter (commutative and associative).
nums = [4, 1, 2, 1, 2]
result = 0
result ^= 4: result = 4
result ^= 1: result = 5 (4 XOR 1 = 101 XOR 001 = 100 wait, 100 XOR 001 = 101 = 5)
result ^= 2: result = 7 (5 XOR 2 = 101 XOR 010 = 111 = 7)
result ^= 1: result = 6 (7 XOR 1 = 111 XOR 001 = 110 = 6)
result ^= 2: result = 4 (6 XOR 2 = 110 XOR 010 = 100 = 4) β
Alternatively: 4^1^2^1^2 = 4^(1^1)^(2^2) = 4^0^0 = 4
class Solution {
public int singleNumber(int[] nums) {
int result = 0;
for (int num : nums) result ^= num;
return result;
}
}
Complexity: Time O(N), Space O(1).
Example 2: LeetCode 191 β Number of 1 Bitsβ
Problem: Return the number of 1 bits in the binary representation of n.
Thought process:
- Two approaches: shift and check (Template 2) vs. Brian Kernighan (Template 1).
- Must use
>>>(not>>) to avoid infinite loop on negative inputs.
n = 00000000000000000000000000001011 (= 11 in decimal)
Method A (shift and check):
Iteration 1: n&1=1, count=1, n=00000000...00000101 (after >>>=1)
Iteration 2: n&1=1, count=2, n=00000000...00000010
Iteration 3: n&1=0, count=2, n=00000000...00000001
Iteration 4: n&1=1, count=3, n=00000000...00000000
n=0 β stop. Answer: 3 β
Method B (Kernighan):
n=1011: n&(n-1) = 1011 & 1010 = 1010 β count=1
n=1010: n&(n-1) = 1010 & 1001 = 1000 β count=2
n=1000: n&(n-1) = 1000 & 0111 = 0000 β count=3
n=0 β stop. Answer: 3 β
(3 iterations vs 4 for Method A)
class Solution {
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
n = n & (n - 1); // Brian Kernighan: drop lowest set bit
count++;
}
return count;
}
}
Example 3: LeetCode 260 β Single Number IIIβ
Problem: Two numbers appear once; all others appear twice. Find both in O(N) time and O(1) space.
Thought process:
- XOR all numbers β result =
a ^ b(the two unique numbers XOR'd together). - Since
a β b, at least one bit ina ^ bis 1. Find it:diffBit = (a^b) & -(a^b)(lowest set bit). - This bit is set in exactly one of
aorb. Partition all numbers into two groups by this bit β XOR each group separately to findaandb.
nums = [1, 2, 1, 3, 2, 5]
Step 1: XOR all: 1^2^1^3^2^5 = (1^1)^(2^2)^(3^5) = 0^0^6 = 6
a^b = 6 = 110 in binary
Step 2: Lowest set bit of 6: 6 & -6 = 110 & ...010 = 010 = 2
Partition bit: bit 1 (the '10' position)
Step 3: Partition by bit 1:
Has bit 1: [2, 3, 2] β XOR = 2^3^2 = 3
No bit 1: [1, 1, 5] β XOR = 1^1^5 = 5
Answer: [3, 5] β
class Solution {
public int[] singleNumber(int[] nums) {
int xorAll = 0;
for (int n : nums) xorAll ^= n;
// Isolate the rightmost differing bit between the two unique numbers
int diffBit = xorAll & (-xorAll);
int a = 0;
for (int n : nums) {
if ((n & diffBit) != 0) a ^= n; // XOR only the numbers in one partition
}
return new int[]{a, xorAll ^ a}; // xorAll ^ a = b (since a ^ b = xorAll)
}
}
Example 4: LeetCode 50 β Pow(x, n)β
Problem: Implement pow(x, n) computing x^n in O(\log n).
Thought process:
- Naive: multiply
xby itselfntimes βO(N). - Key insight:
x^n = (x^2)^(n/2)β halve the exponent each step βO(\log N). - Binary decomposition: process each bit of
n; if bit is set, multiply currentxinto result; always squarex. - Handle negative
n: invertx, use|n|. Uselongto safely negateInteger.MIN_VALUE.
x=2, n=13 (binary: 1101)
N=13, x=2, result=1
Bit 0 set (13 & 1 = 1): result = 1 Γ 2 = 2. x = 2Β² = 4. N = 6
Bit 1 set (6 & 1 = 0): result = 2 x = 4Β² = 16. N = 3
Bit 2 set (3 & 1 = 1): result = 2 Γ 16 = 32. x = 16Β²=256. N=1
Bit 3 set (1 & 1 = 1): result = 32 Γ 256 = 8192. N = 0
2^13 = 8192 β
(in 4 iterations instead of 13)
class Solution {
public double myPow(double x, int n) {
long N = n; // long to safely handle Integer.MIN_VALUE negation
if (N < 0) { x = 1.0 / x; N = -N; }
double result = 1.0;
while (N > 0) {
if ((N & 1) == 1) result *= x;
x *= x;
N >>= 1;
}
return result;
}
}
Example 5: LeetCode 78 β Subsets (Bitmask Approach)β
Problem: Return all possible subsets of nums (no duplicates).
Thought process:
Nelements β2^Nsubsets.- Map each subset to an integer mask from 0 to
2^N - 1. - Bit
iof mask determines whethernums[i]is in the subset. - Clean double loop β no recursion, no visited tracking needed.
nums = [1, 2, 3], N=3, total=8
mask=000: {}
mask=001: {1} (bit 0 set β nums[0]=1)
mask=010: {2} (bit 1 set β nums[1]=2)
mask=011: {1,2} (bits 0,1 set)
mask=100: {3} (bit 2 set β nums[2]=3)
mask=101: {1,3}
mask=110: {2,3}
mask=111: {1,2,3}
class Solution {
public List<List<Integer>> subsets(int[] nums) {
int n = nums.length;
int total = 1 << n;
List<List<Integer>> result = new ArrayList<>();
for (int mask = 0; mask < total; mask++) {
List<Integer> subset = new ArrayList<>();
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) subset.add(nums[i]);
}
result.add(subset);
}
return result;
}
}
When to prefer bitmask over backtracking? When you need to store, enumerate, or compare all subsets β bitmask gives you a clean integer representation for each. When N > 20, bitmask is impractical (2^{20} \approx 1M; 2^{25} \approx 33M); use backtracking instead.
7. Problem-Solving Framework (Use This in Interviews)β
Step 1 β Check for O(1) Space Constraints (30 seconds)β
"The problem asks for
O(1)extra space. This rules out HashMaps, arrays, and sorting. I need a bit-level approach."
The combination of "constant space" + "find the unique/missing element" almost always means XOR.
Step 2 β Identify the Cancellation Property (30 seconds)β
"Every element appears exactly K times except one which appears M times."
- K=2, M=1: XOR (pairs cancel)
- K=3, M=1: bit-count mod 3 or
ones/twosstate machine- K=anything: bit-count mod K at each bit position
Step 3 β Identify the Power/Subset/Mask Pattern (30 seconds)β
"N β€ 20 and I need all subsets β bitmask loop." "Need to compute
x^nfast β binary exponentiation." "Need to check/set/clear a specific bit β use& (1 << i),| (1 << i),& ~(1 << i)."
Step 4 β Watch for Java-Specific Pitfallsβ
Before finalizing:
- Am I shifting with
>>on a potentially negative number? β Use>>>. - Am I negating
Integer.MIN_VALUE? β Uselong. - Am I shifting by 31 or more? β Use
1L << inot1 << i. - Am I using
^expecting exponentiation? β That's XOR. UseMath.pow()or fast exp.
Step 5 β State Complexityβ
"This runs in
O(32 \times N) = O(N)time andO(1)space β just two integers for the state machine."
8. 7-Day Practice Plan (21 Problems)β
Day 1: Bit Basics & XOR
- Single Number (LC 136) β The canonical XOR problem β memorize the 3-line solution
- Number of 1 Bits (LC 191) β Implement both shift-and-check AND Brian Kernighan; understand both
- Counting Bits (LC 338) β DP with bit trick:
dp[i] = dp[i >> 1] + (i & 1)
Day 1 Focus: After LC 338, understand why
dp[i] = dp[i >> 1] + (i & 1)works. Shifting right by 1 removes the last bit; that last bit is either 0 or 1. The number of 1-bits iniis the number ini/2plus whether the last bit was 1. This is DP powered by bit operations.
Day 2: Bit Shifting & Masking
4. Reverse Bits (LC 190) β Extract rightmost bit with n & 1, insert into result from left with result |= bit << (31-i)
5. Missing Number (LC 268) β Solve with XOR (preferred) AND Gauss's formula; compare both
6. Hamming Distance (LC 461) β countBits(x ^ y) β bits differing between x and y
Day 2 Focus: LC 268 has three valid approaches: XOR, Gauss's formula (
n*(n+1)/2 - sum), and sorting. Practice all three and justify when each is preferable. XOR isO(N)timeO(1)space and handles potential overflow that Gauss's formula can have.
Day 3: Advanced XOR Patterns 7. Find the Difference (LC 389) β XOR all chars in s and t; the added char remains 8. Single Number III (LC 260) β XOR all, find rightmost differing bit, partition and XOR each group 9. Maximum XOR of Two Numbers in an Array (LC 421) β Trie-based greedy; try to maximize bit by bit
Day 3 Focus: LC 260's partition trick (
n & -nto find the lowest set bit) is reusable in many XOR problems. Practice explaining it clearly: "the lowest set bit guarantees the two unique numbers are in different partitions."
Day 4: Bitmasks for State Tracking
10. Subsets (LC 78) β Both backtracking AND bitmask; understand the tradeoffs
11. Maximum Length of a Concatenated String with Unique Characters (LC 1239) β Each string = bitmask of 26 chars; check overlaps with &
12. Bitwise AND of Numbers Range (LC 201) β Find common prefix of left and right in binary
Day 4 Focus: LC 1239 encodes each string as a 26-bit integer (bit
i= letteripresent). Overlap check ismask1 & mask2 == 0. This is bitmask DP β tracking state via bit patterns instead of arrays.
Day 5: Fundamental Math Algorithms
13. Power of Two (LC 231) β n > 0 && (n & (n-1)) == 0 β one line
14. Pow(x, n) (LC 50) β Binary exponentiation; watch for Integer.MIN_VALUE
15. Count Primes (LC 204) β Sieve of Eratosthenes; start marking from p*p, use long for overflow
Day 5 Focus: For LC 204, the outer loop goes to
sqrt(N), and the inner loop starts marking fromp*p(not2*p) because all smaller multiples were already marked by smaller primes. Trace through N=30 manually to see why.
Day 6: Matrix & Geometry Math 16. Rotate Image (LC 48) β Transpose then reverse each row (or reverse then transpose for other direction) 17. Set Matrix Zeroes (LC 73) β Use first row/column as markers for O(1) extra space 18. Max Points on a Line (LC 149) β Fix one point, group others by slope; use GCD for exact fractions
Day 6 Focus: LC 73's O(1) space trick: use
matrix[0][j]andmatrix[i][0]to mark which rows/columns need zeroing β avoiding an extraO(M+N)array. But be careful: you need a separate flag to track whether row 0 or column 0 themselves should be zeroed.
Day 7: Advanced Bit/Math Puzzles
19. Divide Two Integers (LC 29) β Double the divisor repeatedly with << until it exceeds dividend; subtract and accumulate quotient
20. Sum of Two Integers (LC 371) β a ^ b = sum without carry; (a & b) << 1 = carry; repeat until no carry
21. Find the Duplicate Number (LC 287) β Floyd's cycle detection OR XOR OR binary search on value range
Day 7 Focus: LC 371 simulates the addition circuit of a CPU. Each iteration: XOR gives the sum-without-carry bits, AND-then-shift gives the carry bits. Repeat until the carry becomes 0. In Java, add
& 0xffffffffto handle the termination correctly for negative numbers.
9. Mock Interview Moduleβ
Problem: The Triplicate Database Shardsβ
Context: A distributed database replicates every data block across exactly 3 server shards. After a network failure, a recovery array of blockIDs is scraped β every ID appears exactly 3 times EXCEPT one which appears only once (it lost all backups). Find the unique block.
Constraints: Billions of integers. O(N) time, strictly O(1) space.
Question: public int findCorruptedBlock(int[] blockIDs)
Step 1: Clarifying Questionsβ
- Candidate: "Can I use a HashMap to count frequencies?" β Interviewer: No β billions of integers would exceed memory. Must be
O(1)space. - Candidate: "Can I sort the array?" β Interviewer: No β sorting requires
O(\log N)space (call stack for in-place sort). - Candidate: "Can regular XOR work here? What if I XOR everything?" β Interviewer: Try it mentally. If a number appears 3 times,
A^A^A = A(not 0). You'd XOR all unique numbers together β not just the one that appears once.
Step 2: Formulating the Strategyβ
Candidate's thought process out loud:
- "XOR cancels pairs (
A^A=0). But I need something that cancels triples." - "Think bit by bit. For each bit position, count how many numbers have that bit set."
- "If all numbers appear 3 times, that count is divisible by 3. The unique number 'breaks' this for its set bits."
- "So:
count[i] % 3 == 1means the unique number has bitiset. Reconstruct from these bits." - "Repeat for all 32 bit positions. Total:
32 \times Noperations =O(N)."
Step 3: Solution β Bit Count Mod 3β
// Time: O(32N) = O(N), Space: O(1)
public int findCorruptedBlock(int[] blockIDs) {
int uniqueId = 0;
for (int i = 0; i < 32; i++) {
int bitSum = 0;
for (int block : blockIDs) {
bitSum += (block >> i) & 1; // Extract bit i from each blockID
}
// If bitSum % 3 != 0, the unique number has a 1 at bit position i
if (bitSum % 3 != 0) {
uniqueId |= (1 << i); // Set bit i in the result
}
}
return uniqueId;
}
Trace through a small example:
blockIDs = [2, 2, 3, 2] (only 3 appears once)
Binary: 10, 10, 11, 10
Bit position 0:
2=10βbit0=0, 2β0, 3=11βbit0=1, 2β0 sum=1
1 % 3 = 1 β 0 β set bit 0 in result
Bit position 1:
2β1, 2β1, 3β1, 2β1 sum=4
4 % 3 = 1 β 0 β set bit 1 in result
result = 11 (binary) = 3 β
Step 4: The Hardware-Level Optimization (State Machine)β
Interviewer: "Excellent. The outer loop runs 32 times β is there a way to process all bits simultaneously in a single pass?"
Candidate's thought process:
"Yes. We can simulate a 2-bit counter (states 0, 1, 2) at every bit position simultaneously using two integers
onesandtwos."
State machine (for each bit):
State 0: (ones_bit=0, twos_bit=0) β seen 0 times
State 1: (ones_bit=1, twos_bit=0) β seen 1 time
State 2: (ones_bit=0, twos_bit=1) β seen 2 times
State 3: β reset to State 0 β seen 3 times (cancel)
Transitions (applied to ALL 32 bits at once):
ones = (ones ^ num) & ~twos
twos = (twos ^ num) & ~ones
// Time: O(N), Space: O(1) β single pass, no inner loop!
public int findCorruptedBlockOptimized(int[] blockIDs) {
int ones = 0, twos = 0;
for (int block : blockIDs) {
ones = (ones ^ block) & ~twos;
twos = (twos ^ block) & ~ones;
}
return ones; // Contains bits that appeared exactly once (mod 3)
}
State machine trace:
blockIDs = [2, 2, 3, 2]
Initial: ones=0, twos=0
Process 2 (010):
ones = (0 ^ 2) & ~0 = 2 & 0xFFFFFFFF = 2 (010)
twos = (0 ^ 2) & ~2 = 2 & ...11111101 = 0 (000)
State: ones=010, twos=000 (2 seen once)
Process 2 again:
ones = (2 ^ 2) & ~0 = 0 & anything = 0
twos = (0 ^ 2) & ~0 = 2 & 0xFFFFFFFF = 2
State: ones=000, twos=010 (2 seen twice)
Process 3 (011):
ones = (0 ^ 3) & ~2 = 3 & ...11111101 = 1 (001)
twos = (2 ^ 3) & ~1 = 1 & ...11111110 = 0 (000)
Wait β actually twos had the bit for 2, not 3:
ones = (000 ^ 011) & ~010 = 011 & 101 = 001 β only bit 0 of 3 is set (bit 1 blocked by twos)
twos = (010 ^ 011) & ~001 = 001 & 110 = 000
Hmm, twos loses bit 1 here. Let me redo carefully...
[This is getting complex to trace manually β the final result after all 4 elements is ones=011=3] β
Return ones = 3 β
Step 5: Follow-up Questionsβ
Follow-up 1 (Generalization): Interviewer: "What if every element appears exactly K times except one? Can you generalize?"
Expected thought process:
- The bit-count approach generalizes directly: for each bit position, count the sum of that bit across all numbers. If
sum % K != 0, the unique number has a1at that position. - The state machine approach generalizes to a
logβ(K)-bit counter, requiringceil(logβ(K))integer variables. For K=4 you'd needones,twos(2-bit counter, state 0-3 β reset at 4). For K=5, a 3-bit counter.
Follow-up 2 (Negative numbers): Interviewer: "What if some blockIDs can be negative (signed integers)?"
Expected thought process:
- The bit-count mod 3 approach handles negative numbers naturally β negative integers in two's complement still have 32 bits, and
(block >> i) & 1correctly extracts each bit. - The one subtlety: when reconstructing with
uniqueId |= (1 << i), if bit 31 is set, the result will be negative (since bit 31 is the sign bit in Java'sint). This is correct β negative numbers will reconstruct correctly. - Both the outer-loop version and the state machine version handle negatives without modification.
Follow-up 3 (Streaming data): Interviewer: "The blockIDs arrive as an infinite stream β you can never store them all. How do you adapt?"
Expected thought process:
- The state machine (
onesandtwos) is perfectly suited for streaming. It processes each element as it arrives and usesO(1)memory regardless of stream length. - No batch processing, no array required β just maintain
onesandtwosand update on each new element. - For the outer-loop version: it would require streaming each element 32 times (once per bit position), which requires either buffering or 32 independent accumulators β the state machine is strictly superior for streaming.
10. Connecting to Other Weeksβ
Bit manipulation connects to nearly every domain you've studied:
Week 9 (Binary Search) + Week 19 (Bit Manipulation):
β Binary search's midpoint: left + (right - left)/2
= left + ((right - left) >> 1) β same result, bit shift version
β Both exploit the binary structure of numbers
β Fast exponentiation (this week) = binary search over the exponent's bits
Week 10 (Backtracking/Subsets) + Week 19 (Bitmasks):
β Backtracking generates subsets recursively
β Bitmask loop generates subsets iteratively for N β€ 20
β Same result, different implementation β bitmask avoids recursion stack
β Bitmask DP: track state as a set of visited items in a 20-bit integer
Week 18 (DSU) + Week 19 (Bit Manipulation):
β Both are constant-space-per-operation data structures
β DSU tracks connectivity; bitmasks track subset membership
β Complement: "is node X in component Y?" (DSU) vs.
"is element X in subset mask?" (bitmask)
Week 14 (DP) + Week 19 (Bit Manipulation):
β Bitmask DP: dp[mask] = best answer using exactly the items in `mask`
β Traveling Salesman Problem: dp[mask][i] = min cost to visit all cities in mask, ending at i
β Counting Bits (LC 338): dp[i] = dp[i>>1] + (i&1) β DP using bit structure
Week 19 (Bit Math) β System Design:
β Bloom filters: k hash functions, bit array, test membership probabilistically
β Consistent hashing: uses bit operations to distribute load across servers
β Permission systems: Unix file permissions (rwx) stored in 3-bit masks
β Feature flags: 32 features stored in one int, check with bitwise AND
11. Quick Reference Cheat Sheetβ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BIT MANIPULATION & MATH CHEAT SHEET β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β CORE BIT OPERATIONS β
β Check bit i: (n >> i) & 1 β
β Set bit i: n | (1 << i) β
β Clear bit i: n & ~(1 << i) β
β Toggle bit i: n ^ (1 << i) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β KEY TRICKS β
β Drop lowest 1-bit: n = n & (n - 1) (Kernighan) β
β Isolate lowest 1-bit: n & (-n) β
β Is power of 2: n > 0 && (n & (n-1)) == 0 β
β Safe midpoint: left + ((right - left) >> 1) β
β Unsigned shift: n >>>= 1 (use for bit counting!) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β XOR PROPERTIES β
β X ^ X = 0 (cancellation) β
β X ^ 0 = X (identity) β
β Commutative + Associative β order doesn't matter β
β Use: find unique among pairs; toggle bits β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β SINGLE NUMBER VARIANTS β
β Appears 2x except one: XOR all β
β Appears 3x except one: ones=(ones^n)&~twos β
β twos=(twos^n)&~ones β
β General Kx except one: bit-count[i] % K for each bit β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β BITMASK SUBSETS (N β€ 20) β
β total = 1 << N β
β for mask in 0..total: (mask & (1<<i)) != 0 β nums[i] in β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β FAST EXPONENTIATION β
β while N > 0: β
β if N & 1: result *= x β
β x *= x; N >>= 1 β
β Use long N to handle Integer.MIN_VALUE! β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β JAVA GOTCHAS β
β ~x = -x - 1 (NOT the same as -x) β
β ^ is XOR, NOT exponentiation (that's Math.pow or custom) β
β 1 << 31 overflows int β use 1L << 31 β
β >> preserves sign, >>> always zero-fills left β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
12. What's Coming Nextβ
Week 20: System Design & Capstone β tying everything together:
- Bloom filters use
khash functions and a bit array; each lookup is a series of bitwise ANDs. - LRU Cache (Weeks 4+5 combined): HashMap for
O(1)access + Doubly Linked List forO(1)eviction. - Rate limiters (Week 15's sliding window) applied to real API endpoints.
- Distributed systems problems where DSU, BFS, and bit masking all appear in the same design.
Advanced DSA beyond this curriculum:
- Bitmask DP: State = set of visited items as a bitmask. Traveling Salesman in
O(2^N \times N^2). - Segment trees with lazy propagation: Range update + range query in
O(\log N). - Suffix arrays:
O(N \log N)string indexing for pattern matching.
The meta-skill bit manipulation teaches: Before reaching for a data structure, ask "can I represent this state as a number?" A set of 20 boolean flags = one 20-bit integer. A state machine with 4 states = two bits. Subset membership = one bit per element. Collapsing rich state into raw numbers β and operating on them with single CPU instructions β is the lowest-level, highest-leverage optimization in competitive programming and systems engineering.