Bit Manipulation
Scope — How integers are represented in bits (two’s complement, fixed width, shifts) and the operations and tricks built on that — masks, XOR identities, lowest set bit, subset enumeration, and bitmask DP. See also: bit_manipulation_examples.md — the fourteen worked problems behind these techniques; math.md — numeric manipulation without bits; combinatorics_math_patterns.md — counting; dp.md — the wider DP catalogue that bitmask DP belongs to.
LeetCode Problem Lists
Overview
Bit manipulation operates directly on the binary representation of integers. Because
each operation is a single CPU instruction, bitwise tricks turn many O(n) scans into
O(1) arithmetic, and let a small integer act as a compact set (bitmask) of up to
32/64 flags.
Key Properties
- Time Complexity:
O(1)per bit op;O(number of bits)≈O(32)for whole-word scans - Space Complexity:
O(1)— a mask reuses one integer instead of an array/set - Core Idea: read/flip individual bits with
&|^~<<>>; XOR cancels pairs (a ^ a = 0) - When to Use: pairing/cancellation problems, counting set bits, subset enumeration
(bitmask), power-of-two checks, adding without
+, packing flags into one number
Quick Reference — the tricks you must memorize Priority 5 of 5 — Must know — expect it in almost every loop
| Goal | Expression |
|---|---|
Test if i-th bit is set |
(x >> i) & 1 |
Set i-th bit |
x | (1 << i) |
Clear i-th bit |
x & ~(1 << i) |
Toggle i-th bit |
x ^ (1 << i) |
| Lowest set bit (isolate) | x & -x |
| Clear lowest set bit | x & (x - 1) |
| Is power of two? | x > 0 && (x & (x - 1)) == 0 |
| Is even? | (x & 1) == 0 |
| XOR self-cancel | a ^ a = 0, a ^ 0 = a |
References
- LeetCode — Bit Manipulation card
- bit_manipulation.md
- leetcode-easy-bitwise-xor-summary
- bit VS byte VS char — bit widths, with Java examples
- Python operators — precedence table
0) CS Foundations Priority 5 of 5 — Must know — expect it in almost every loop
Almost every “clever” bit trick is a direct consequence of how a machine stores an integer. Five facts cover it — learn these and the tricks below stop needing memorisation.
| # | Fact | What it explains |
|---|---|---|
| 1 | A number is digits × place values; binary’s place values are powers of two | reading and writing binary/hex by hand |
| 2 | An int is a fixed-width 32-bit box that wraps around |
overflow, MIN_VALUE, why masks exist |
| 3 | Negatives are stored in two’s complement | ~x = -x-1, x & -x, >> on negatives |
| 4 | Shifting is multiplying/dividing by powers of two | <<, >>, >>> and their edge cases |
| 5 | The bits of an int are a subset of 32 items | bitmask, subset enumeration, bitmask DP |
0-1) Place values, binary, and hex
The value of a base-X number is decided by each digit and its position
(ref):
123.45 (base 10) = 1*10^2 + 2*10^1 + 3*10^0 + 4*10^-1 + 5*10^-2
720.5 (base 8) = 7*8^2 + 2*8^1 + 0*8^0 + 5*8^-1
1011 (base 2) = 1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 = 8 + 0 + 2 + 1 = 11
To read binary, add the place values that carry a 1. To write it, halve repeatedly and record the remainders bottom-up. That is the entire conversion.
Hex is binary in groups of four. One hex digit = 4 bits (a nibble), so a 32-bit int
is exactly 8 hex digits — which is why masks are written that way:
1011 1101 -> B D -> 0xBD = 11*16 + 13 = 189
each nibble maps to one hex digit — no arithmetic needed
0xFFFFFFFF = 32 ones 0x7FFFFFFF = 31 ones = Integer.MAX_VALUE
Powers of two are worth knowing cold — they appear both as constraints and as masks:
n |
4 | 8 | 10 | 16 | 20 | 31 | 32 |
|---|---|---|---|---|---|---|---|
2^n |
16 | 256 | 1024 | 65536 | ~1 million | 2 147 483 648 | 4 294 967 296 |


0-2) Fixed width — an int is a 32-bit box
| Java type | Width | Range |
|---|---|---|
byte |
8 bits | -128 … 127 |
char |
16 bits | 0 … 65535 (unsigned) |
int |
32 bits | -2^31 … 2^31 - 1 |
long |
64 bits | -2^63 … 2^63 - 1 |
- Bit
icarries place value2^i. In anint, bit 31 is the sign bit, so the usable magnitude is 31 bits — this is why so many solutions loopfor i in 0..31. - Anything that leaves the box wraps silently:
Integer.MAX_VALUE + 1 == Integer.MIN_VALUE. A problem statement saying “assume the result fits in a 32-bit integer” is telling you this is the edge case being tested. 1 << 31is already negative. Write1L << 31when you want the value2^31.
0-3) Two’s complement — how negatives are stored Priority 5 of 5 — Must know — expect it in almost every loop
The rule: -x is stored as ~x + 1 — flip every bit, then add one. Equivalently,
the pattern for -x is the unsigned value 2^32 - x.
(traces are 8-bit for space; a real int is the same picture, 32 wide)
5 = 0000 0101
~5 = 1111 1010 flip every bit
-5 = 1111 1011 ... + 1
check: 5 + (-5) = 1 0000 0000 -> the carry falls out of the box, leaving 0 ✓
Why this representation and not a sign bit + magnitude? Because one adder handles both
signs — a + b is the same circuit whether the operands are positive or negative, and
there is only one zero.
Three facts drop straight out of it:
| Fact | Where it shows up |
|---|---|
~x == -x - 1 |
rewriting ~ when a language has no unsigned type |
| top bit set ⇔ negative | the for i in 0..31 bit-column loops |
x >> 31 is 0 (non-negative) or -1 (negative) |
branchless abs, sign extraction |
The asymmetry that bites: the range holds one more negative than positive, so
Integer.MIN_VALUE has no positive twin — -Integer.MIN_VALUE and
Math.abs(Integer.MIN_VALUE) are both still Integer.MIN_VALUE. That single value is the
hidden test case in LC 29 (Divide Two Integers); handle it before you negate anything.
0-4) Why x & (x - 1) and x & -x work
Both fall out of two’s complement. Derive them once and you never have to memorise which is which:
x = 0101 1000 lowest set bit is bit 3
x - 1 = 0101 0111 borrowing flips that 1 to 0, and every 0 below it to 1
-x = 1010 1000 = ~x + 1
above bit 3 at bit 3 below bit 3
x vs x-1 : identical 1 vs 0 complementary
x vs -x : complementary 1 vs 1 both 0
x & (x-1) = 0101 0000 above survives; bit 3 and everything under it AND to 0
x & (-x) = 0000 1000 above ANDs to 0; only bit 3 survives
So x & (x - 1) means “drop the lowest 1” (loop it → Brian Kernighan’s popcount, §1-3)
and x & -x means “keep only the lowest 1” (also the step rule of a Fenwick tree — see
binary_indexed_tree.md).
0-5) Shifts: left, arithmetic right, and logical right
| Op | Name | Shifts in | Effect |
|---|---|---|---|
x << n |
left shift | zeros on the right | x * 2^n; bits pushed off the top are lost |
x >> n |
arithmetic right shift | copies of the sign bit | floor(x / 2^n) |
x >>> n |
logical right shift (Java only) | zeros | treats the pattern as unsigned |
-8 >> 1 = -4 1111 1000 -> 1111 1100 sign preserved
-8 >>> 1 = 2147483644 1111 1000 -> 0111 1100 sign bit treated as just another bit
When you need >>>: any loop that walks all 32 bits of a possibly-negative int —
LC 190 (Reverse Bits), LC 191 (Number of 1 Bits), LC 338. With >>, a negative number
shifts in 1s forever and while (x != 0) never terminates.
Two more rules that surprise people, both verified above:
- Java masks the shift count to 5 bits:
1 << 32is1 << 0, i.e.1— not0. Shift along(6-bit count), or split the shift. +binds tighter than<<:x << 1 + 2isx << 3. See §0-7.
0-6) Python is not Java here Priority 5 of 5 — Must know — expect it in almost every loop
Python’s ints are arbitrary precision and behave as if they had infinitely many sign
bits. There is no box, so there is nothing to overflow — and no >>>, because there is no
top bit to stop at.
Java (int, 32-bit) |
Python (unbounded) | |
|---|---|---|
1 << 31 |
-2147483648 (it hit the sign bit) |
2147483648 |
Integer.MAX_VALUE + 1 |
wraps to MIN_VALUE |
just keeps growing |
-1 >> 100 |
-1 |
-1 (infinite sign bits) |
| logical right shift | x >>> n |
none — mask by hand |
~5 |
-6 |
-6 (same) |
So a Python loop that relies on 32-bit wrap-around has to simulate the box:
# python
MASK = 0xFFFFFFFF # keep only the low 32 bits
INT_MAX = 0x7FFFFFFF # 2^31 - 1
def to_signed(x):
"""read a masked 32-bit pattern back as a signed Python int"""
return x if x <= INT_MAX else ~(x ^ MASK)
This is the whole reason LC 371 (Sum of Two Integers) looks so much worse in Python than in
Java: the carry loop is the same three lines, but every step needs & MASK and the result
needs to_signed.
Rule of thumb: in Python, iterate
for i in range(32): (x >> i) & 1rather thanwhile x:wheneverxcan be negative — thewhilenever ends.
0-7) Precedence — parenthesise everything Priority 4 of 5 — High value — a gap here costs you rounds
Tightest to loosest — this chain is the same in C, Java and Python:
~ -> * / % -> + - -> << >> -> & -> ^ -> |
Two things go wrong with it:
x << 1 + 2 parses as x << (1 + 2) -> x << 3, not (x << 1) + 2
a ^ b + 1 parses as a ^ (b + 1)
Comparison is the one place the languages disagree: C and Java slot == between
>> and &, Python puts it below |. So x & 1 == 0 means three different things:
| Language | x & 1 == 0 |
Outcome |
|---|---|---|
| C / C++ | x & (1 == 0) → x & 0 |
silently always 0 |
| Java | x & (1 == 0) → int & boolean |
compile error (bad operand types) |
| Python | (x & 1) == 0 |
correct — comparison binds looser than & here |
Never rely on which one you are in. Write (x & 1) == 0.
0-8) A bitmask is a set
The final foundation: subsets of n items are in one-to-one correspondence with the
integers 0 … 2^n - 1. Once you see that, “bitmask” needs no further explanation — every
set operation is one instruction.
| Set language | Bit language |
|---|---|
S = {} / S = {0..n-1} |
0 / (1 << n) - 1 |
i ∈ S |
(mask >> i) & 1 |
S ∪ {i} / S \ {i} / toggle i |
mask | (1<<i) / mask & ~(1<<i) / mask ^ (1<<i) |
A ∪ B / A ∩ B / A \ B |
a | b / a & b / a & ~b |
A ⊆ B |
(a & b) == a |
A ∩ B = ∅ |
(a & b) == 0 |
|S| |
Integer.bitCount(mask) / bin(mask).count("1") |
complement within n items |
mask ^ ((1 << n) - 1) |
Two counting facts tell you whether a bitmask solution fits the constraints:
- there are
2^nsubsets, son ≤ ~20for anO(2^n · n)DP (2^20 ≈ 10^6); - summed over every mask, the number of submasks is
3^n, not4^n— which is what makes thesub = (sub - 1) & maskloop in §2-1 affordable.
0-9) The library helpers — value vs index Priority 3 of 5 — Worth knowing — usually a variant of a must-know pattern
Every language ships these, and reaching for the wrong one is a silent bug rather than a
compile error. The split that causes it: some helpers return a value (a pattern with one
bit set), others return an index (0…31). They are never interchangeable.
x = 0b101000 (= 40)
Integer.highestOneBit(x) = 0b100000 = 32 <- a VALUE
Integer.lowestOneBit(x) = 0b001000 = 8 <- a VALUE (exactly x & -x)
Integer.numberOfTrailingZeros(x) = 3 <- an INDEX
31 - Integer.numberOfLeadingZeros(x) = 5 <- an INDEX
The two columns below are equivalent only for x >= 0 — see Negatives below for why.
| Goal | Java (x >= 0) |
Python (x >= 0) |
|---|---|---|
| popcount | Integer.bitCount(x) |
x.bit_count() (3.10+), else bin(x).count("1") |
| lowest set bit — value | Integer.lowestOneBit(x) |
x & -x |
| lowest set bit — index | Integer.numberOfTrailingZeros(x) |
(x & -x).bit_length() - 1 |
| highest set bit — value | Integer.highestOneBit(x) |
1 << (x.bit_length() - 1) |
highest set bit — index = floor(log2 x) |
31 - Integer.numberOfLeadingZeros(x) |
x.bit_length() - 1 |
| reverse all 32 bits | Integer.reverse(x) |
none — loop it (LC 190) |
| show the bits | Integer.toBinaryString(x) |
bin(x), or format(x, "032b") |
| parse binary | Integer.parseInt(s, 2) |
int(s, 2) |
Negatives — where the two columns stop agreeing
Java’s helpers read a 32-bit two’s-complement pattern, so the sign bit is just another
bit. Python’s bit_length() and bit_count() read the absolute value and have no sign
bit at all (§0-6). The rows do not all break equally:
x = -19 Java Python
popcount 30 3 <- DIVERGES (Java counts the sign bits)
lowest set bit (value) 1 1 <- agrees
lowest set bit (index) 0 0 <- agrees
highest set bit (value) -2147483648 16 <- DIVERGES (Java: always the sign bit)
highest set bit (index) 31 4 <- DIVERGES (Java: always 31)
The lowest-bit rows survive because x & -x is the same arithmetic in both languages.
The highest-bit rows and popcount do not: for any negative int, Java’s highest set
bit is the sign bit, so the value is always Integer.MIN_VALUE and the index always 31.
Mask to 32 bits first (x & 0xFFFFFFFF) if you need Java’s answer out of Python.
The zero cases split along the value/index line too — and this is the part that bites:
value helpers -> Integer.highestOneBit(0) = 0
Integer.lowestOneBit(0) = 0
raw counts -> Integer.numberOfTrailingZeros(0) = 32 <- a count, not a position
Integer.numberOfLeadingZeros(0) = 32
derived index -> 31 - Integer.numberOfLeadingZeros(0) = -1
(0).bit_length() - 1 (Python) = -1
The counts are honest — there really are 32 zero bits in 0. The bug appears the moment you
use a count as a bit position, and the two directions fail differently: the trailing-zero
count is the lowest-set-bit index in the table above, so it hands you 32, and
1 << 32 is masked back to 1 by Java
(§0-5) — a wrong answer, not a crash.
The leading-zero count is turned into an index by 31 - …, so it hands you -1, and
1 << -1 throws or shifts by 31 depending on the language.
So the guard is not “before every count” — it is x == 0 before treating a count as a
position. Integer.bitCount(0), and the two value helpers, need no guard at all.
Python’s log2 is bit_length(), not math.log2. Floats carry 53 bits of mantissa, so
the moment an int needs more precision than that, rounding hands you an off-by-one:
# python
# IDEA: floor(log2 x) == x.bit_length() - 1, exactly, for every x > 0
x = (1 << 53) - 1
x.bit_length() - 1 # 52 <- correct
int(math.log2(x)) # 53 <- WRONG: log2 rounded up to 53.0
x = (1 << 64) - 1
x.bit_length() - 1 # 63 <- correct
int(math.log2(x)) # 64 <- WRONG
Know the manual version too. “Count the set bits without Integer.bitCount” is not a
trick question — it is the whole of LC 191. The x &= (x - 1) loop is in
§1-3; say the library call out loud, then write
the loop.
0-10) XOR prefix, and the 0..n closed form Priority 4 of 5 — High value — a gap here costs you rounds
XOR is addition with the carries thrown away (§0-4), so every prefix-sum technique has an XOR twin — and the XOR one is simpler, because XOR is its own inverse and there is no subtraction step.
Range XOR by prefix
pre[0] = 0
pre[i+1] = pre[i] ^ a[i]
a[l] ^ a[l+1] ^ ... ^ a[r] = pre[r+1] ^ pre[l]
Why ^ and not -: every element before l appears twice in pre[r+1] ^ pre[l],
once from each side, so it cancels itself. With sums you must subtract; with XOR the same
operator undoes itself.
# python
# IDEA: pre[i+1] = XOR of a[0..i]; any range XOR is then one operation
# time = O(n) build + O(1) per query, space = O(n)
def build(a):
pre = [0] * (len(a) + 1)
for i, v in enumerate(a):
pre[i + 1] = pre[i] ^ v
return pre
def range_xor(pre, l, r): # inclusive [l, r]
return pre[r + 1] ^ pre[l]
XOR of 0..n in O(1)
Every aligned block of four cancels itself, which collapses the whole prefix to a lookup
on n % 4:
4k = ...00
4k+1 = ...01 (4k) ^ (4k+1) = 1 <- differ only in bit 0
4k+2 = ...10
4k+3 = ...11 (4k+2) ^ (4k+3) = 1 <- differ only in bit 0
1 ^ 1 = 0 <- so each aligned quadruple vanishes
Only the tail after the last complete block survives:
n % 4 |
0 ^ 1 ^ … ^ n |
|---|---|
0 |
n |
1 |
1 |
2 |
n + 1 |
3 |
0 |
# python
# IDEA: aligned blocks of 4 cancel; only n % 4 decides what is left
# time = O(1), space = O(1)
def xor_to(n): # XOR of 0..n (identical to 1..n, since 0 changes nothing)
return [n, 1, n + 1, 0][n % 4]
def xor_range(l, r): # XOR of l..r
return xor_to(r) ^ xor_to(l - 1)
xor_to covers 1..n as well — XOR-ing in 0 changes nothing
(§0-8 has the identity: a ^ 0 = a).
Where it shows up: any “XOR of a range” query, and as the O(1) replacement for the
x1 = 1 ^ 2 ^ … ^ n loop in LC 268 (Missing Number). LC 2683 (Neighboring Bitwise XOR)
is the prefix idea run backwards. LC 1310 — XOR Queries of a Subarray is the canonical
prefix-XOR drill; it has no solution in this repo yet.
0-11) Branchless idioms Priority 3 of 5 — Worth knowing — usually a variant of a must-know pattern
One expression does most of the work: x >> 31 is 0 for a non-negative int and -1
(all ones) for a negative one (§0-3).
An all-ones mask ANDs to keep, an all-zeros mask ANDs to drop — that is how a branch
becomes arithmetic.
| Goal | Expression | Why it works |
|---|---|---|
do a and b have opposite signs? |
(a ^ b) < 0 |
XOR’s sign bit is 1 exactly when the two sign bits differ — and no overflow, unlike a * b < 0 |
abs(x) |
(x ^ (x >> 31)) - (x >> 31) |
x ≥ 0 → (x ^ 0) - 0 = x; x < 0 → (x ^ -1) - (-1) = ~x + 1 = -x |
sign of x as 0 / -1 |
x >> 31 |
the sign bit smeared across all 32 |
is x a multiple of 2^k? |
(x & ((1 << k) - 1)) == 0 |
the low k bits are the remainder |
round x down to a multiple of 2^k |
x & ~((1 << k) - 1) |
clear the remainder bits |
abs inherits the MIN_VALUE trap, exactly like Math.abs: both return
Integer.MIN_VALUE unchanged, because it has no positive twin
(§0-3). Branchless does not rescue you here.
The one to never ship — XOR swap
// java
// IDEA: swap without a temporary. Correct ONLY when the two operands are distinct.
a ^= b; b ^= a; a ^= b;
It is the classic “look, no temp variable” answer, and it destroys the value when the two operands alias:
swap(arr, i, j) with i == j, arr[i] = 7
arr[i] ^= arr[j] -> 7 ^ 7 = 0 both names point at the same slot
arr[j] ^= arr[i] -> 0 ^ 0 = 0
arr[i] ^= arr[j] -> 0 ^ 0 = 0 the 7 is gone
Any partition step that can call swap(i, i) — several standard quicksort and Dutch-flag
partitions do — is silently zeroed by it. Use a temporary. The extra variable was never
the bottleneck, and no interviewer has ever awarded a point for removing it.
0-12) Derive it yourself
The point of §0 is that these are consequences, not vocabulary. Cover the right-hand column and rebuild each one from two’s complement and place values; if you can do that, you can reconstruct any trick in this file after forgetting it.
Every row assumes x >= 0, which is how these appear in problems. The two that are
outright meaningless on a negative are the Gray code and the adjacent-ones test — in Python
(-1) ^ (-1 >> 1) is 0 and (-1) & (-1 >> 1) is -1, because the sign bits run forever
(§0-6).
| Expression | What it does, and where it comes from |
|---|---|
x & (x - 1) |
clears the lowest set bit — x-1 borrows through the trailing zeros, so at and below that bit the two disagree |
x & -x |
isolates the lowest set bit — -x = ~x + 1, so above that bit the two are complementary |
x & (x + 1) |
clears the trailing ones — the mirror of x & (x - 1) |
x | (x + 1) |
sets the lowest clear bit — carry propagates up to the first 0 |
x ^ (x >> 1) |
the Gray code of x — adjacent codes differ in one bit (LC 89) |
x & (x >> 1) |
non-zero ⇔ x has two adjacent 1s |
(x >> i) & 1 |
reads bit i — shift it down to position 0, mask off the rest |
x ^ ((1 << n) - 1) |
complement within n bits — XOR against a field of ones flips each |
(x & (x - 1)) == 0 |
x is a power of two or zero — one set bit at most; add x > 0 |
x >> 31 |
0 or -1 — the sign bit smeared to full width |
Two rules for using this table honestly: say what the expression does before you check,
and name the input that would break it. (x & (x - 1)) == 0 accepting 0 is the single
most common answer people get half-right.
1) Core Operations
1-1) The 6 operators
| Op | Name | Rule | Example (4-bit) |
|---|---|---|---|
& |
AND | 1 only if both bits 1 | 0110 & 1010 = 0010 |
| |
OR | 1 if either bit 1 | 0110 | 1010 = 1110 |
^ |
XOR | 1 if bits differ | 0110 ^ 1010 = 1100 |
~ |
NOT | flip every bit (~x = -x - 1) |
~0110 = ...1001 |
<< |
left shift | append n zeros → x * 2^n |
0011 << 1 = 0110 |
>> |
right shift | drop n low bits → x // 2^n |
0110 >> 1 = 0011 |
XOR identities (the heart of many LC problems):
a ^ a = 0,a ^ 0 = a, XOR is commutative & associative → XOR-ing a whole list cancels every value that appears an even number of times, leaving only the odd-count one.
1-2) Single-bit tricks (with code)
// java
int testBit(int x, int i) { return (x >> i) & 1; } // 1 if bit i is set, else 0
int setBit(int x, int i) { return x | (1 << i); } // force bit i to 1
int clearBit(int x, int i) { return x & ~(1 << i); } // force bit i to 0
int toggleBit(int x, int i){ return x ^ (1 << i); } // flip bit i
int lowestSetBit(int x) { return x & -x; } // isolate lowest 1-bit
int clearLowestBit(int x) { return x & (x - 1); } // turn OFF lowest 1-bit
# python
def test_bit(x, i): return (x >> i) & 1 # 1 if bit i is set, else 0
def set_bit(x, i): return x | (1 << i) # force bit i to 1
def clear_bit(x, i): return x & ~(1 << i) # force bit i to 0
def toggle_bit(x, i): return x ^ (1 << i) # flip bit i
def lowest_set_bit(x): return x & -x # isolate lowest 1-bit
def clear_lowest(x): return x & (x - 1) # turn OFF lowest 1-bit
1-3) Counting set bits (population count)
Key Idea: x & (x - 1) clears the lowest set bit (why: §0-4), so the loop
runs once per 1-bit (Brian Kernighan’s algorithm) → O(popcount) instead of O(32).
// java
// IDEA: each `x &= (x - 1)` removes exactly one set bit
public int countBits(int x) {
int count = 0;
while (x != 0) {
x &= (x - 1); // clear lowest set bit
count++;
}
return count;
// built-in: Integer.bitCount(x)
}
# python
# IDEA: each `x &= (x - 1)` removes exactly one set bit
def count_bits(x):
count = 0
while x:
x &= (x - 1) # clear lowest set bit
count += 1
return count
# built-in: bin(x).count("1")
Visual trace — count_bits(12), 12 = 1100:
x = 1100 x & (x-1) = 1100 & 1011 = 1000 count = 1
x = 1000 x & (x-1) = 1000 & 0111 = 0000 count = 2
x = 0000 stop → 2 set bits
1-4) Counting over bit COLUMNS — LC 461 / LC 477
Pattern: instead of looping over pairs of numbers, loop over the 32 bit positions and
ask what each column contributes. This turns many O(n^2)-looking problems into O(32n).
Key Idea: at bit position i, if ones numbers have that bit set and n - ones do not,
then exactly ones * (n - ones) pairs differ at that bit. Sum over all 32 positions.
// java
// LC 461 - Hamming Distance (the 2-number base case)
// IDEA: differing bits of x and y are exactly the set bits of x ^ y
// time = O(popcount), space = O(1)
class Solution {
public int hammingDistance(int x, int y) {
int diff = x ^ y, count = 0;
while (diff != 0) {
diff &= (diff - 1); // clear lowest set bit
count++;
}
return count; // built-in: Integer.bitCount(x ^ y)
}
}
// LC 477 - Total Hamming Distance (all pairs)
// IDEA: per bit column, ones * (n - ones) pairs differ there
// time = O(32 * N), space = O(1)
class Solution2 {
public int totalHammingDistance(int[] nums) {
int n = nums.length, total = 0;
for (int i = 0; i < 32; i++) {
int ones = 0;
for (int x : nums) ones += (x >> i) & 1; // count 1s in column i
total += ones * (n - ones); // each 1 pairs with each 0
}
return total;
}
}
# python
# LC 461 - Hamming Distance
# time = O(popcount), space = O(1)
class Solution(object):
def hammingDistance(self, x, y):
diff, count = x ^ y, 0
while diff:
diff &= diff - 1 # clear lowest set bit
count += 1
return count # built-in: bin(x ^ y).count("1")
# LC 477 - Total Hamming Distance
# IDEA: per bit column, ones * (n - ones) pairs differ there
# time = O(32 * N), space = O(1)
class Solution2(object):
def totalHammingDistance(self, nums):
n, total = len(nums), 0
for i in range(32):
ones = sum((x >> i) & 1 for x in nums) # count 1s in column i
total += ones * (n - ones)
return total
Why it works — [4, 14, 2] = 00100, 01110, 00010:
bit column : 0 1 2 3
ones : 0 2 2 1 n = 3
zeros : 3 1 1 2
pairs : 0*3 2*1 2*1 1*2 -> 0 + 2 + 2 + 2 = 6
1-5) A bitmask as a CHARACTER SET — LC 318 Priority 4 of 5 — High value — a gap here costs you rounds
Pattern: a lowercase-letter set fits in 26 bits, so a whole word becomes ONE int.
Then set questions become single instructions:
| Set question | Bit expression |
|---|---|
| do two words share a letter? | (maskA & maskB) != 0 |
| are they disjoint? | (maskA & maskB) == 0 |
| union of the letters | maskA | maskB |
| how many distinct letters? | Integer.bitCount(mask) / bin(mask).count("1") |
| does the word repeat a letter? | while building: (mask & bit) != 0 |
This replaces a per-pair O(len) string comparison with an O(1) AND.
// java
// LC 318 - Maximum Product of Word Lengths
// IDEA: encode each word's letters as a 26-bit mask; two words share no letter iff (mA & mB) == 0
// time = O(N * L + N^2), space = O(N)
class Solution {
public int maxProduct(String[] words) {
int n = words.length;
int[] mask = new int[n];
for (int i = 0; i < n; i++) {
for (char c : words[i].toCharArray()) {
mask[i] |= 1 << (c - 'a'); // add letter c to the set
}
}
int best = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if ((mask[i] & mask[j]) == 0) { // disjoint letter sets
best = Math.max(best, words[i].length() * words[j].length());
}
}
}
return best;
}
}
# python
# LC 318 - Maximum Product of Word Lengths
# IDEA: encode each word's letters as a 26-bit mask; disjoint iff (mA & mB) == 0
# time = O(N * L + N^2), space = O(N)
class Solution(object):
def maxProduct(self, words):
masks = []
for w in words:
m = 0
for c in w:
m |= 1 << (ord(c) - ord('a')) # add letter c to the set
masks.append(m)
best = 0
for i in range(len(words)):
for j in range(i + 1, len(words)):
if masks[i] & masks[j] == 0: # no shared letter
best = max(best, len(words[i]) * len(words[j]))
return best
Variation A — build up a union of disjoint masks (LC 1239)
Twist: instead of picking only two disjoint words, greedily grow every reachable
union. Keep a list of achievable masks; a word can join a mask only if cur & m == 0.
// java
// LC 1239 - Maximum Length of a Concatenated String with Unique Characters
// IDEA: keep every reachable "union of disjoint words" mask; answer = max popcount
// time = O(2^N * 26), space = O(2^N)
class Solution {
public int maxLength(List<String> arr) {
List<Integer> masks = new ArrayList<>();
masks.add(0); // empty selection
int best = 0;
for (String s : arr) {
int m = 0;
boolean dup = false;
for (char c : s.toCharArray()) {
int bit = 1 << (c - 'a');
if ((m & bit) != 0) { dup = true; break; } // word itself repeats a letter
m |= bit;
}
if (dup) continue;
// iterate BACKWARDS over the snapshot so newly added masks aren't reused this round
for (int i = masks.size() - 1; i >= 0; i--) {
int cur = masks.get(i);
if ((cur & m) != 0) continue; // overlap -> can't concatenate
masks.add(cur | m);
best = Math.max(best, Integer.bitCount(cur | m));
}
}
return best;
}
}
# python
# LC 1239 - Maximum Length of a Concatenated String with Unique Characters
# IDEA: keep every reachable "union of disjoint words" mask; answer = max popcount
# time = O(2^N * 26), space = O(2^N)
class Solution(object):
def maxLength(self, arr):
masks, best = [0], 0 # 0 = empty selection
for s in arr:
m, dup = 0, False
for c in s:
bit = 1 << (ord(c) - ord('a'))
if m & bit: # word itself repeats a letter
dup = True
break
m |= bit
if dup:
continue
for cur in list(masks): # snapshot, so this word is used at most once
if cur & m: # overlap -> can't concatenate
continue
masks.append(cur | m)
best = max(best, bin(cur | m).count("1"))
return best
Variation B — pack fixed-width symbols into a rolling int key (LC 187)
Twist: the alphabet is only 4 symbols (A C G T), so each char needs 2 bits and a
10-char window is a 20-bit integer. Slide the window with hash = ((hash << 2) | code) & mask
— an O(1) rolling key instead of hashing a 10-char substring every step.
// java
// LC 187 - Repeated DNA Sequences
// IDEA: 2 bits per base -> a 10-char window is one 20-bit int; roll it with shift + mask
// time = O(N), space = O(N)
class Solution {
public List<String> findRepeatedDnaSequences(String s) {
int L = 10, n = s.length();
List<String> res = new ArrayList<>();
if (n <= L) return res;
int mask = (1 << (2 * L)) - 1; // keep only the low 20 bits
int hash = 0;
Map<Integer, Integer> seen = new HashMap<>();
for (int i = 0; i < n; i++) {
hash = ((hash << 2) | "ACGT".indexOf(s.charAt(i))) & mask; // push new base, drop oldest
if (i >= L - 1) {
int c = seen.getOrDefault(hash, 0) + 1;
seen.put(hash, c);
if (c == 2) res.add(s.substring(i - L + 1, i + 1)); // report once
}
}
return res;
}
}
# python
# LC 187 - Repeated DNA Sequences
# IDEA: 2 bits per base -> a 10-char window is one 20-bit int; roll it with shift + mask
# time = O(N), space = O(N)
class Solution(object):
def findRepeatedDnaSequences(self, s):
L, n = 10, len(s)
if n <= L:
return []
code = {'A': 0, 'C': 1, 'G': 2, 'T': 3}
mask = (1 << (2 * L)) - 1 # keep only the low 20 bits
h, seen, res = 0, {}, []
for i, c in enumerate(s):
h = ((h << 2) | code[c]) & mask # push new base, drop the oldest
if i >= L - 1:
seen[h] = seen.get(h, 0) + 1
if seen[h] == 2: # report each repeat exactly once
res.append(s[i - L + 1:i + 1])
return res
More letter-mask practice (same 26-bit encoding, no new technique): LC 1255 (Maximum Score Words Formed by Letters), LC 2135 (Count Words Obtained After Adding a Letter), LC 1684 (Count the Number of Consistent Strings —
word & ~allowed == 0).
2) Bitmask DP
A bitmask lets an integer represent a set of visited/chosen items (bit i set ⇔ item
i in the set). When a DP state needs “which subset of ≤ ~20 items have I used”, the mask
is the state — enabling exponential subset problems to run in O(2^n · n).
2-1) Subset enumeration (LC 78 recap)
Iterating mask from 0 to 2^n − 1 visits every subset exactly once; bit tests pick
members (see §12 of the worked examples). Handy mask idioms:
# python
mask & (1 << i) # is item i in the subset?
mask | (1 << i) # add item i
mask & ~(1 << i) # remove item i
bin(mask).count("1") # size of the subset
sub = (sub - 1) & mask # enumerate all SUB-masks of `mask` (classic trick)
2-2) TSP-style bitmask DP (Held–Karp)
The Travelling Salesman family is the canonical bitmask DP: dp[mask][i] = min cost of a
path that has visited exactly the cities in mask and currently sits at city i.
state : dp[mask][i] mask = set of visited cities, i = current city
trans : dp[mask | (1<<j)][j] = min( dp[mask][i] + dist[i][j] ) for j not in mask
answer: min over i of dp[FULL][i] (+ dist[i][start] for a cycle)
time : O(2^n · n^2) space : O(2^n · n)
// java
// Held–Karp TSP skeleton: dp[mask][i] = min cost visiting `mask`, ending at city i
int tsp(int[][] dist) {
int n = dist.length, FULL = (1 << n) - 1;
int[][] dp = new int[1 << n][n];
for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE / 2);
dp[1][0] = 0; // start at city 0, only it visited
for (int mask = 1; mask <= FULL; mask++) {
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) == 0) continue; // i must be in mask
for (int j = 0; j < n; j++) {
if ((mask & (1 << j)) != 0) continue; // j must NOT be visited yet
int next = mask | (1 << j);
dp[next][j] = Math.min(dp[next][j], dp[mask][i] + dist[i][j]);
}
}
}
int ans = Integer.MAX_VALUE;
for (int i = 0; i < n; i++) ans = Math.min(ans, dp[FULL][i] + dist[i][0]); // close cycle
return ans;
}
When to reach for bitmask DP:
nis small (≤ ~20 so2^nis tractable) and the state is “which subset have I used/visited”. Related LC: 847 (Shortest Path Visiting All Nodes), 1349 (Maximum Students Taking Exam), 691 (Stickers to Spell Word), 526 (Beautiful Arrangement).
2-3) “Fill buckets one at a time” bitmask DP — LC 698 Priority 5 of 5 — Must know — expect it in almost every loop
Pattern: partition-into-k-equal-groups problems look like they need k nested searches.
The trick is to stop tracking which bucket you are filling and only track:
state : dp[mask] = how full the CURRENT bucket is, given `mask` items are already placed
(-1 = mask unreachable)
key : sum(mask) is fixed by the mask, so the bucket index is implied —
every time the running bucket hits `target` it wraps to 0 and a new bucket starts
trans : dp[mask | (1<<i)] = (dp[mask] + nums[i]) % target, allowed iff dp[mask] + nums[i] <= target
answer: dp[FULL] == 0 (all items used AND the last bucket closed exactly)
time : O(2^n · n) space : O(2^n)
Key Idea: % target is what makes the “start the next bucket” transition free — no extra
state dimension for the bucket counter.
Pruning that matters: sort nums ascending, then break (not continue) as soon as
dp[mask] + nums[i] > target — every later item is larger and also fails.
// java
// LC 698 - Partition to K Equal Sum Subsets
// IDEA: dp[mask] = fill level of the current bucket; % target rolls over to the next bucket
// time = O(2^n * n), space = O(2^n)
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
int sum = 0;
for (int x : nums) sum += x;
if (sum % k != 0) return false; // can't split evenly
int target = sum / k, n = nums.length;
Arrays.sort(nums); // ascending -> enables the `break` prune
if (nums[n - 1] > target) return false; // one item already overflows a bucket
int FULL = (1 << n) - 1;
int[] dp = new int[1 << n];
Arrays.fill(dp, -1); // -1 = state not reachable
dp[0] = 0; // nothing placed, empty bucket
for (int mask = 0; mask <= FULL; mask++) {
if (dp[mask] < 0) continue; // unreachable
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) continue; // item i already used
if (dp[mask] + nums[i] > target) break; // sorted -> all later items fail too
int next = mask | (1 << i);
if (dp[next] < 0) {
dp[next] = (dp[mask] + nums[i]) % target; // == 0 -> bucket closed, start next
}
}
}
return dp[FULL] == 0; // every item used and the final bucket landed exactly on target
}
}
# python
# LC 698 - Partition to K Equal Sum Subsets
# IDEA: dp[mask] = fill level of the current bucket; % target rolls over to the next bucket
# time = O(2^n * n), space = O(2^n)
class Solution(object):
def canPartitionKSubsets(self, nums, k):
total = sum(nums)
if total % k: # can't split evenly
return False
target, n = total // k, len(nums)
nums.sort() # ascending -> enables the `break` prune
if nums[-1] > target: # one item already overflows a bucket
return False
FULL = (1 << n) - 1
dp = [-1] * (1 << n) # -1 = state not reachable
dp[0] = 0 # nothing placed, empty bucket
for mask in range(FULL + 1):
if dp[mask] < 0:
continue
for i in range(n):
if mask & (1 << i): # item i already used
continue
if dp[mask] + nums[i] > target:
break # sorted -> all later items fail too
nxt = mask | (1 << i)
if dp[nxt] < 0:
dp[nxt] = (dp[mask] + nums[i]) % target # 0 -> bucket closed
return dp[FULL] == 0
Visual trace — nums = [1,2,2,3] (sorted), k = 2, target = 4.
Bit i = nums[i] used; only the winning path is shown (the loop also fills the other masks):
mask 0000 dp=0 place nums[0]=1 -> dp[0001] = 1
mask 0001 dp=1 place nums[3]=3 -> dp[1001] = (1+3) % 4 = 0 (bucket closed!)
mask 0011 dp=3 place nums[2]=2 -> 3+2 = 5 > 4 -> break (dead branch)
mask 1001 dp=0 place nums[1]=2 -> dp[1011] = 2
mask 1011 dp=2 place nums[2]=2 -> dp[1111] = (2+2) % 4 = 0
dp[FULL] == 0 -> TRUE ([1,3] and [2,2])
Variation — same template, k hard-coded (LC 473)
Twist: LC 473 (Matchsticks to Square) is LC 698 with k = 4; nothing else changes.
Variation — mask as a GAME state instead of a DP table (LC 464)
Twist: for LC 464 (Can I Win) the mask is “which of the 1..maxChoosable numbers are already
taken”, and the recursion is minimax rather than a cost: win(mask) is true if any unused
i either reaches the total immediately or leaves the opponent in a losing state
!win(mask | (1 << (i-1))). Memoize on mask alone — the remaining total is implied by it.
Prune first with maxChoosable * (maxChoosable + 1) / 2 < desiredTotal → nobody can win.
More bitmask-DP practice: LC 1125 (Smallest Sufficient Team — set cover,
dp[skillMask]), LC 980 (Unique Paths III — mask of visited cells), LC 864 (Shortest Path to Get All Keys — BFS state =(cell, keyMask)), LC 1494 (Parallel Courses II — iterate submasks of the currently-available course set withsub = (sub - 1) & mask).
Not bitmask DP, but bit-adjacent: LC 421 (Maximum XOR of Two Numbers in an Array) and LC 1707 (Maximum XOR With an Element From Array) are solved with a binary/XOR trie — see
trie.mdrather than duplicating it here.
Worked Examples
Nineteen problems live in bit_manipulation_examples.md, grouped by which property of the bit operators they lean on:
| Group | The property | Problems |
|---|---|---|
| XOR — cancelling pairs | x ^ x == 0, so anything paired disappears |
LC 136, 137, 260, 268 |
| Counting & transforming bits | x & (x-1) clears the lowest set bit |
LC 191, 338, 190, 231 |
| Arithmetic without arithmetic | XOR is addition without carry; AND finds the carry | LC 371, 67, 29 |
| Enumerating & constructing | an integer is a subset, and counting up visits every one | LC 78, 89, 201 |
| Bit-field surgery | build a mask, clear the field, OR the new bits in | CtCI 5.1–5.7 |
Summary
Pick the technique from the question
| The problem says… | Reach for | Section |
|---|---|---|
| “every element appears twice except one” | XOR the whole array | XOR — cancelling pairs |
“count the 1 bits” / “for every i in 0..n” |
x & (x-1) loop, or DP on i >> 1 |
§1-3 |
| “sum over all pairs” of a bit property | loop the 32 bit columns, not the pairs | §1-4 |
| lowercase words, “share a letter” / “unique characters” | a 26-bit letter mask | §1-5 |
| a small fixed alphabet + a sliding window | pack k bits per symbol, roll with << and a mask |
Variation B |
“choose a subset”, n ≤ ~20 |
dp[mask], bitmask DP |
§2 |
“partition into k equal groups” |
dp[mask] = fill level, % target |
§2-3 |
“XOR of a subarray” / “XOR of 1..n”, asked repeatedly |
XOR prefix array, or the n % 4 closed form |
§0-10 |
“add / divide without + or /” |
XOR = sum, AND = carry | Arithmetic without arithmetic |
“set / clear / replace bits i..j” |
a 111..000..111 mask, then OR the field in |
Bit-field surgery |
| “next number with the same number of 1s” | flip the rightmost non-trailing zero, repack the ones | Bit-field surgery |
| “maximum XOR of two numbers” | binary trie — see trie.md | — |
The five bugs that fail a bit-manipulation submission
while (x != 0) x >>= 1on a negativeint— arithmetic shift feeds in 1s forever. Use>>>in Java, orfor i in range(32)in Python. (§0-5, §0-6)- Missing parentheses — write
(x & 1) == 0, neverx & 1 == 0. (§0-7) Integer.MIN_VALUEhas no positive twin —Math.absand unary-both return it unchanged, so negate-then-divide silently breaks. (§0-3)1 << ioverflows ati >= 31— use1L << i(and remember Java masks the shift count, so1 << 32 == 1). (§0-2, §0-5)- Porting a 32-bit loop to Python unchanged — Python never overflows, so every step
needs
& 0xFFFFFFFFand the result needs converting back. (§0-6)