Bit Manipulation Pattern: Algorithmic Intuition, Sub-patterns & C++ Guide
Work directly with the binary form of numbers: XOR cancels pairs, masks pick bits, shifts multiply/divide by 2.
4 sub-patterns Β· 15 questions
What is this pattern?
Bit manipulation means working on a number's binary digits directly, one bit at a time, instead of on its decimal value. Every bit is independent of the others under AND, OR and XOR, so a hard question about numbers often becomes 32 easy questions about single bits.
The handful of facts that solve most problems:
- XOR cancels:
x ^ x = 0andx ^ 0 = x, in any order. XOR a list together and every value that appears twice disappears. n & (n β 1)removes the lowest 1-bit:12 = 1100β1100 & 1011 = 1000. Loop it to count 1-bits; one step to zero means a power of two.x & βxisolates the lowest 1-bit:12 & β12 = 0100 = 4. Use it to split numbers into two groups by one bit.- Shifts read and build bits:
(n >> i) & 1reads biti;1 << iis a number with only bitiset. - A mask is a set: with
n β€ 20items, an int whose bitisays "itemiis chosen" lets you loop over all subsets or run DP over them.
Think of a number as a row of light switches. AND keeps a light on only if it is on in both rows, OR turns it on if it is on in either, and XOR flips a switch each time it is touched β so a switch touched twice ends where it started. That last fact is why pairs vanish under XOR.
See it move
Green = a bit turned on, red = a bit turned off because it was already on (a pair cancelling), blue = the 1-bits of the current number.
nums = [4, 1, 2, 1, 2]step 1/7- x
- 0
Start with x = 0, because 0 ^ v = v. Each number will flip the bits of x where it has a 1.
Purple = the lowest 1-bit of n, yellow = the bits that subtracting 1 flips, red = the bit the AND removes. The last step shows x & βx, which keeps only that bit (negative numbers are shown in two's complement, last 6 bits).
n = 44step 1/6- n
- 44 = 101100
- 1-bits removed
- 0
n = 44 = 101100. Instead of checking all 6 bits, remove one 1-bit per step with n & (n β 1) and count the steps.
How do I recognise it?
"Every element appears twice (or three times) except one" β and the question asks for O(1) extra space.
"Power of two", "number of 1 bits", "Hamming distance", "reverse the bits".
"XOR of a subarray / range", "bitwise AND of a range" β think prefix XOR or the common binary prefix.
"AND / OR of a combination is greater than 0" β look at each bit separately and count.
Small n (β€ 20) with "visit all", "choose a subset", "assign each item" β a bitmask as the DP state.
"Without using
*,/or%" β build the result with shifts and adds.
Looks like Bit Manipulation, but isn't
XOR works too, but the classic lesson is placing each value at its index. Bit tricks shine when values are not a permutation of 0..n.
Choosing a best partner for each number bit by bit from the top is a walk down a binary trie. Maximum XOR of ANY subset is different: that is the XOR basis in this pattern.
Listing subsets is fine either way. Use a bitmask as a DP state only when the same set of chosen items is reached many times (TSP, assignment).
It is the "subarray sum equals k" pattern with XOR in place of +. The bit fact used is just that prefix XORs cancel.
How would I discover it from scratch?
Don't memorise βuse bit manipulationβ. Ask these questions in order β the pattern falls out.
Treat numbers as plain values: count each value with a nested loop or a hash map, divide by 2 in a loop, try every subset. Correct, but it ignores that each bit can be handled on its own.
Sub-patterns
Same core idea, different rule for βvalidβ. Learn them in this order.
XOR cancellation
4 questionsValues that appear an even number of times cancel under XOR, leaving the odd one out. XOR also undoes itself: if c = a ^ b then a = c ^ b.
Bit counting / powers
5 questionsQuestions about one number's bits: how many are set, whether exactly one is set, their reverse, or building a quotient from powers of two.
Range bitwise properties
3 questionsA bitwise operation over a range or a group of numbers, answered without touching every number again.
Bitmask state
3 questionsAn integer stands for a set of items (bit i = item i chosen) or for a code to be generated. With up to ~20 items, you can loop over or run DP across all 2^n sets.
Don't mix these up
n & (n β 1) removes the lowest 1-bit. x & βx keeps only the lowest 1-bit. n & 1 just reads bit 0 (odd or even).
XOR counts each bit mod 2. If the others appear three times, count mod 3 instead; XOR-ing everything gives a wrong value.
>> on a negative int copies the sign bit in, so a loop "until n == 0" never ends; 1 << 31 overflows int. Use unsigned / uint32_t.
Pair β bitwise trie, best partner per number. Subset β XOR basis (Gaussian elimination over bits).
5 & 7 = 5, but 6 is in the range and clears bit 0, so AND of 5..7 is 4. Every bit below the first differing bit becomes 0.
Code templates (C++)
XOR cancellation and per-bit counting
XOR-all for "pairs except one"; split by x & βx for two singles; count each bit mod k when others appear k times. Rebuild in unsigned so bit 31 (negatives) is safe.
// Every value appears twice except one: pairs cancel (x ^ x = 0, x ^ 0 = x).
int singleOfPairs(const vector<int>& a) {
int x = 0;
for (int v : a) x ^= v;
return x;
}
// Exactly two values appear once: XOR of all = a ^ b, which is non-zero.
// Any 1-bit of it differs between a and b; split the array by that bit.
vector<int> twoSingles(const vector<int>& a) {
unsigned all = 0;
for (int v : a) all ^= v;
unsigned low = all & -all; // lowest 1-bit (unsigned: safe for INT_MIN)
int g0 = 0, g1 = 0;
for (int v : a) ((unsigned)v & low ? g1 : g0) ^= v; // each pair lands in one group
return {g0, g1};
}
// Every value appears k times except one: count each bit mod k.
int singleOfK(const vector<int>& a, int k) {
unsigned res = 0;
for (int b = 0; b < 32; b++) {
int cnt = 0;
for (int v : a) cnt += ((unsigned)v >> b) & 1;
if (cnt % k) res |= 1u << b; // 1u: bit 31 must not overflow int
}
return (int)res;
}Bit tricks you reuse everywhere
n & (n β 1) drops the lowest 1, x & βx keeps it, (n >> i) & 1 reads bit i. Shift in unsigned / uint32_t whenever bit 31 can be involved.
// n & (n - 1) removes the lowest 1-bit: 1100 -> 1000.
int countOnes(unsigned n) {
int c = 0;
while (n) { n &= n - 1; c++; } // runs once per 1-bit
return c;
}
// A power of two has exactly one 1-bit. Check n > 0 first (0, negatives, INT_MIN).
bool isPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; }
// x & -x keeps only the lowest 1-bit: 1100 -> 0100.
unsigned lowestBit(unsigned x) { return x & -x; }
// Read, set, clear, flip bit i (0 = rightmost). Use unsigned when i can be 31.
bool getBit(unsigned n, int i) { return (n >> i) & 1; }
unsigned setBit(unsigned n, int i) { return n | (1u << i); }
unsigned clearBit(unsigned n, int i) { return n & ~(1u << i); }
unsigned flipBit(unsigned n, int i) { return n ^ (1u << i); }
// Bits of every number 0..n: i without its last bit is i >> 1, already solved.
vector<int> countBitsUpTo(int n) {
vector<int> bits(n + 1, 0);
for (int i = 1; i <= n; i++) bits[i] = bits[i >> 1] + (i & 1);
return bits;
}
// Reverse all 32 bits: exactly 32 steps, so leading zeros become trailing zeros.
uint32_t reverse32(uint32_t n) {
uint32_t r = 0;
for (int i = 0; i < 32; i++) { r = (r << 1) | (n & 1); n >>= 1; }
return r;
}Range properties: prefix XOR, common prefix, bit columns
XOR of a range = two prefix XORs. AND of a range keeps only the common binary prefix of its ends. "AND > 0" questions count numbers per bit column.
// XOR of a[l..r] in O(1) after O(N) setup: the shared prefix cancels.
struct PrefixXor {
vector<int> p; // p[i] = a[0] ^ ... ^ a[i-1], p[0] = 0
PrefixXor(const vector<int>& a) : p(a.size() + 1, 0) {
for (size_t i = 0; i < a.size(); i++) p[i + 1] = p[i] ^ a[i];
}
int query(int l, int r) const { return p[r + 1] ^ p[l]; }
};
// AND of every number in [left, right] = their common binary prefix.
// Each lower bit is 0 somewhere in the range, so it becomes 0.
int rangeAnd(int left, int right) {
int shift = 0;
while (left != right) { left >>= 1; right >>= 1; shift++; }
return left << shift;
}
// Largest group with AND > 0: the group must share one bit, so count per bit.
int largestSharingABit(const vector<int>& a) {
int best = 0;
for (int b = 0; b < 31; b++) {
int cnt = 0;
for (int v : a) cnt += (v >> b) & 1;
best = max(best, cnt);
}
return best;
}Bitmask DP over subsets
mask >> i & 1 = is item i in; mask | 1 << i = add it. Loop masks in increasing order: adding an item makes the mask bigger, so smaller masks are always done first.
// A mask is a set: bit i = item i is in. n up to ~20.
// in: (mask >> i) & 1 add: mask | (1 << i) size: __builtin_popcount(mask)
void forEachSubset(int n) {
for (int mask = 0; mask < (1 << n); mask++) {
for (int i = 0; i < n; i++)
if ((mask >> i) & 1) { /* item i is in this subset */ }
}
}
// Visit-all DP (TSP shape): dp[mask][last] = best cost to start at 0,
// visit exactly the items in mask, and stand at last.
int visitAll(const vector<vector<int>>& cost) {
int n = cost.size(), FULL = (1 << n) - 1;
const int INF = 1e9;
vector<vector<int>> dp(1 << n, vector<int>(n, INF));
dp[1][0] = 0; // only item 0 visited, standing at 0
for (int mask = 1; mask <= FULL; mask++) // smaller masks are always finished first
for (int last = 0; last < n; last++) {
if (dp[mask][last] == INF) continue;
for (int nxt = 0; nxt < n; nxt++) {
if ((mask >> nxt) & 1) continue;
int nm = mask | (1 << nxt);
dp[nm][nxt] = min(dp[nm][nxt], dp[mask][last] + cost[last][nxt]);
}
}
int best = INF;
for (int last = 0; last < n; last++) best = min(best, dp[FULL][last] + cost[last][0]);
return n == 1 ? 0 : best;
}
// Every submask of mask (for "split a set into two parts" DPs): 3^n in total.
void forEachSubmask(int mask) {
for (int sub = mask; sub > 0; sub = (sub - 1) & mask) { /* use sub */ }
}