2Brute2Best
πŸ”₯0

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

Remember this Β· Bit Manipulation
Trigger
"Every element appears twice except…", power of two, counting set bits, subsets as bitmasks, O(1) space tricks.
Think
What happens to this problem if I look at each bit separately?
Key question
Which bit operation (XOR, AND, OR, shift) cancels or isolates what I need?
A

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 = 0 and x ^ 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 & βˆ’x isolates 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) & 1 reads bit i; 1 << i is a number with only bit i set.
  • A mask is a set: with n ≀ 20 items, an int whose bit i says "item i is 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.

B

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.

XOR cancellation: pairs vanishnums = [4, 1, 2, 1, 2]step 1/7
nums
0
1
2
3
4
4
1
2
1
2
x
2
1
0
0
0
0
Running XOR
x
0
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 & (n βˆ’ 1) removes the lowest 1-bitn = 44step 1/6
n
5
4
3
2
1
0
1
0
1
1
0
0
Count
n
44 = 101100
1-bits removed
0
count the 1-bits of 44

n = 44 = 101100. Instead of checking all 6 bits, remove one 1-bit per step with n & (n βˆ’ 1) and count the steps.

C

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

Find the one missing number in 0..n
β†’ Cyclic Sort (or a sum formula)

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.

Maximum XOR of TWO numbers in an array
β†’ Trie (bitwise trie)

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.

Generate all subsets
β†’ Backtracking (or a mask loop)

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).

Count subarrays whose XOR equals k
β†’ Prefix Sum + hash map

It is the "subarray sum equals k" pattern with XOR in place of +. The bit fact used is just that prefix XORs cancel.

D

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.

E

Sub-patterns

Same core idea, different rule for β€œvalid”. Learn them in this order.

F

Don't mix these up

n & (n βˆ’ 1) vs n & 1 / x & βˆ’x

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).

Pairs (XOR all) vs Triples (count bits mod 3)

XOR counts each bit mod 2. If the others appear three times, count mod 3 instead; XOR-ing everything gives a wrong value.

Signed shift vs Unsigned shift

>> 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.

Maximum XOR of a pair vs Maximum XOR of a subset

Pair β†’ bitwise trie, best partner per number. Subset β†’ XOR basis (Gaussian elimination over bits).

left & right vs AND of the whole range

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.

G

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.

xor-template.cpp
// 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.

bit-tricks-template.cpp
// 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.

range-bits-template.cpp
// 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.

bitmask-template.cpp
// 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 */ }
}