Encode Sets as Integers and Manipulate Them with Single Bitwise Operations
Bit Masks
A bit mask represents a subset of up to 32 items as a single integer, letting you set, clear, toggle, and test membership with O(1) bitwise operations and iterate all subsets in O(2^n) time for bitmask DP.
What you'll learn
- Set, clear, toggle, and test a single bit using shift and AND/OR/XOR
- Iterate over all subsets of a given mask in order
- Recognise the bitmask DP pattern for small-n combinatorial problems
A bit mask stores a subset of items as a single integer: bit k is 1 if item k is in the set, 0 if it is not. With 32-bit integers you can represent any subset of up to 32 items and perform set operations in a single CPU instruction.
The Four Fundamental Operations
Let k be the zero-based index of the bit you want to manipulate.
const set = (mask, k) => mask | (1 << k); // turn bit k ON
const clear = (mask, k) => mask & ~(1 << k); // turn bit k OFF
const toggle = (mask, k) => mask ^ (1 << k); // flip bit k
const test = (mask, k) => (mask >> k) & 1; // 1 if bit k is set, else 0
let m = 0b0000;
m = set(m, 2); console.log(m.toString(2)); // 100
m = set(m, 0); console.log(m.toString(2)); // 101
m = toggle(m, 2); console.log(m.toString(2)); // 001
console.log(test(m, 0)); // 1
console.log(test(m, 3)); // 0 Iterating All Set Bits
To visit only the positions that are ON, repeatedly isolate the lowest set bit
with n & -n, then strip it with n &= n - 1.
function forEachBit(mask, fn) {
let m = mask;
while (m !== 0) {
const lowest = m & -m; // isolate lowest set bit
const k = Math.log2(lowest); // its index
fn(k);
m &= m - 1; // clear that bit
}
}
forEachBit(0b10110, k => console.log(k)); // 1, 2, 4 Iterating All Subsets of a Mask
This loop visits every subset (including empty and the mask itself) in descending order without any extra storage.
function allSubsets(mask) {
const subs = [];
for (let sub = mask; sub > 0; sub = (sub - 1) & mask) {
subs.push(sub);
}
subs.push(0); // include the empty subset
return subs;
}
console.log(allSubsets(0b111).map(s => s.toString(2)));
// ['111', '110', '101', '100', '011', '010', '001', '0'] The trick sub = (sub - 1) & mask moves to the next subset by borrowing a bit
and masking off positions not in the original mask.
Bitmask DP Teaser
When n is small (typically n <= 20), you can let a DP state be a bitmask
representing which items have been used. The classic example is the Travelling
Salesman Problem:
// dp[mask][i] = min cost to visit exactly the cities in `mask`,
// ending at city i. mask has n bits; there are 2^n * n states.
function tspCost(dist) {
const n = dist.length;
const FULL = (1 << n) - 1;
const dp = Array.from({ length: 1 << n }, () => new Array(n).fill(Infinity));
dp[1][0] = 0; // start at city 0, only city 0 visited
for (let mask = 1; mask <= FULL; mask++) {
for (let u = 0; u < n; u++) {
if (!(mask & (1 << u))) continue;
if (dp[mask][u] === Infinity) continue;
for (let v = 0; v < n; v++) {
if (mask & (1 << v)) continue;
const next = mask | (1 << v);
dp[next][v] = Math.min(dp[next][v], dp[mask][u] + dist[u][v]);
}
}
}
let ans = Infinity;
for (let u = 1; u < n; u++) {
ans = Math.min(ans, dp[FULL][u] + dist[u][0]);
}
return ans;
} Up Next
Count how many bits are set in an integer efficiently with Brian Kernighan’s algorithm and the popcount technique.
Counting Bits →