core idea
Operate on a number’s binary representation directly instead of the number’s value. A handful of
identities let &, |, ^, <<, and >> do in one step what a loop, a hash set, or a whole
extra array would otherwise cost you — usually collapsing an O(n)-space solution down to O(1):
| identity | what it does | shows up in |
|---|---|---|
x ^ x == 0 and x ^ 0 == x |
a value cancels itself; XOR is its own inverse | pair-cancellation — “find the loner” |
n & (n - 1) |
clears the lowest set bit | popcount, power-of-two checks |
n & -n |
isolates the lowest set bit (keeps only it) | Fenwick trees, “next set bit” walks |
n << k / n >> k |
multiply / divide by 2^k for a non-negative n |
fast halving in a bit-DP recurrence |
1 << k |
a mask with only bit k set |
test, set, or clear one specific bit |
n > 0 && (n & (n - 1)) == 0 |
true only when n is a power of two |
one-line power-of-two check |
when to reach for it
- The problem demands no extra space, and a
HashSet/Dictionarywould otherwise be the obvious answer — XOR cancellation deletes it entirely. - You’re counting or testing individual bits: “how many 1 bits”, “is this a power of two”, “reverse the bits”.
- The statement forbids the normal arithmetic operators and wants the result built from
&,|,^,<<,>>instead (e.g. “add two integers without+”). - You need an answer for every integer in a range
0..n, and each answer is one bit-shift away from a smaller integer’s answer you’ve already computed.
universal templates
XOR cancellation — pairs vanish, the loner (or the gap) survives:
public int XorCancellation(int[] nums)
{
int result = 0;
foreach (int x in nums)
result ^= x; // x^x=0 for every pair; XOR is commutative/associative,
return result; // so pairing order never matters
}
Clear the lowest set bit — count or process bits one at a time, not 32 times:
public int ClearLowestSetBit(uint n)
{
int count = 0;
while (n != 0)
{
n &= n - 1; // n-1 flips every trailing 0 to 1 and the lowest 1 to 0;
count++; // ANDing with the original n zeroes out just that one bit
}
return count;
}
Bit-DP recurrence — every value’s answer is a smaller value’s answer plus one bit’s worth:
public int[] BitDp(int n)
{
var dp = new int[n + 1];
for (int i = 1; i <= n; i++)
dp[i] = dp[i >> 1] + (i & 1); // i>>1 is smaller than i, so it's already filled in
return dp;
}
the space tell
If a HashSet would solve it in O(n) space, ask whether the duplicates actually cancel in
pairs. Two copies of the same value XOR to zero; three or more don’t, and you’ll need a
different trick (per-bit counting mod 3). When pairs do cancel, you just deleted the
hashmap from your solution.
problems
Four short problems, each one instance of a skeleton above:
XOR everything — pairs cancel, the loner survives.
XOR indices against values — everything cancels except the gap.
n & (n−1) clears the lowest set bit; count the clears.
DP on bits: bits[i] = bits[i >> 1] + (i & 1).
cheat sheet — bits
recognize it
- "constant/no extra space" where a
HashSetwould normally solve it → look for pair-cancellation via XOR - "how many 1 bits", "power of two", "reverse the bits" → the
n & (n - 1)family - problem forbids
+/-and wants the result built from&,|,^,<<,>>instead - an answer is needed for every integer
0..n, and each one is one bit-shift away from a smaller one already computed → bit-DP
key tricks
x ^ x == 0andx ^ 0 == x— XOR cancels duplicate pairs; commutative + associative means order never mattersn & (n - 1)clears the lowest set bit — loop untiln == 0, count the clears, done inpopcount(n)steps not 32n & -nisolates the lowest set bit instead of clearing it- seed the accumulator with
n(or fold in the full index range) before XOR-ing indices against values — Missing Number bits[i] = bits[i >> 1] + (i & 1)— bit-DP: drop the lowest bit, add it back if it was set
common bugs
- assuming XOR cancellation generalizes past pairs — three copies don't vanish,
x ^ x ^ x == x - reaching for a
HashSet<int>when the prompt says "constant extra space" — it passes, but it's the O(n)-space solution you were steered away from - signed right shift (
>>) sign-extending a negative value into an infinite loop — useuintor the>>>operator - off-by-one on array size — an inclusive
0..nrange needsn + 1slots, notn - Gauss-sum (
n * (n + 1) / 2) withoutlong— the multiply overflowsintfor large enoughn