Bit Manipulation and Bitmasking
Set, clear, toggle, test bit; isolate lowest set bit; count set bits; power-of-two test; XOR cancellation; parity; masks; and bit shifts.
Basics
Set, clear, toggle, test bit; isolate lowest set bit; count set bits; power-of-two test; XOR cancellation; parity; masks; and bit shifts. (Module 3.9 introduced the operators.)
Patterns
Single-number variants, subset enumeration, bitmask visited sets, bitwise trie, subset DP, SOS DP, and Gray code.
Output (g++ 11.2, -std=c++17, warnings as errors, AddressSanitizer + UndefinedBehaviorSanitizer; Ubuntu 22.04 on WSL2):
Gray codes list all n-bit values so that consecutive ones differ in exactly one bit (i ^ (i >> 1)). Iterating s = (s − 1) & mask visits every submask in decreasing order; over all masks this totals 3ⁿ steps.
#include <bits/stdc++.h>
using namespace std;
int kernighan(unsigned x) { int c = 0; while (x) { x &= x - 1; ++c; } return c; } // one iteration per set bit
int main() {
vector<int> a = {4, 1, 2, 1, 2};
int single = 0;
for (int x : a) single ^= x; // pairs cancel: x ^ x = 0
cout << "single number in {4,1,2,1,2}: " << single << '\n';
// two numbers appearing once, all others twice
vector<int> b = {1, 2, 1, 3, 2, 5};
int x = 0; for (int v : b) x ^= v; // x = p ^ q
int bit = x & -x; // a bit where p and q differ
int p = 0, q = 0;
for (int v : b) (v & bit ? p : q) ^= v;
cout << "the two singles in {1,2,1,3,2,5}: " << min(p, q) << ' ' << max(p, q) << '\n';
cout << "popcount of 0b1011011: Kernighan " << kernighan(0b1011011) << ", builtin " << __builtin_popcount(0b1011011) << '\n';
// enumerate all submasks of a mask: s = (s - 1) & mask
unsigned mask = 0b1011;
cout << "submasks of 1011:";
for (unsigned s = mask; ; s = (s - 1) & mask) { cout << ' ' << bitset<4>(s); if (s == 0) break; }
cout << "\nGray code n=3:";
for (unsigned i = 0; i < 8
…single number in {4,1,2,1,2}: 4
the two singles in {1,2,1,3,2,5}: 3 5
popcount of 0b1011011: Kernighan 5, builtin 5
submasks of 1011: 1011 1010 1001 1000 0011 0010 0001 0000
Gray code n=3: 000 001 011 010 110 111 101 100
SOS sum for mask 101 = a[000]+a[001]+a[100]+a[101] = 1+2+16+32 = 51Advanced topics
Brian Kernighan's algorithm, popcount, arithmetic bit tricks with proof, bitset acceleration, and undefined/implementation-defined behavior concerns in different languages.
x & (x − 1) clears the lowest set bit: subtracting 1 flips that bit and every zero below it.
x & −x isolates the lowest set bit (two's complement negation = invert and add 1).
std::bitset and word-level operations process 64 bits per instruction — the bitset subset-sum in Module 22 is a “divide by 64” speed-up.
Language differences: in Java >>> is the unsigned right shift; JavaScript bitwise operators work on 32-bit signed integers; Python integers are unbounded, so ~x is −x − 1 and masks must be applied explicitly.
Quiz
? Why does XOR-ing every element find the one that appears once?
+ x ^ x = 0 and XOR is associative and commutative, so pairs cancel. :: The run returned 4 for {4,1,2,1,2}.
- XOR finds the maximum. :: No.
- Because the array is sorted. :: It is not.
- It only works for powers of two. :: Works for any integers.
? How many iterations does Kernighan's popcount loop make?
+ One per set bit :: Each `x &= x − 1` clears the lowest set bit.
- One per bit position :: That is the naive loop.
- log₂ x :: Not exactly.
- Always 32 :: No.
? Which expressions isolate or clear the lowest set bit? (select all that apply)
+ `x & -x` isolates it :: Two's complement.
+ `x & (x - 1)` clears it :: Subtract-one flips the trailing zeros and that bit.
- `x | (x + 1)` isolates it :: That sets the lowest zero bit.
- `x >> 1` clears it :: It shifts every bit.