leetcode / Bit manipulation / Bit manipulation core

Bit manipulation

An integer is a row of switches, and a handful of operators flip, test and combine whole rows at once. XOR cancels equal values, so pairs vanish; n & (n - 1) switches off the lowest 1; a shift moves every bit one place; and AND with a mask asks a yes-or-no question of each position. These problems look like puzzles until you see that each one is a single fact about binary, applied in a loop.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. The operators and their identities
  5. Cancelling pairs
  6. Counting bits for every i
  7. Adding with XOR and AND
  8. Common prefixes and free bits
  9. Worked example
  10. Implementation
  11. Variations and edge cases
  12. Complexity and tradeoffs
  13. Problems
  14. Easy
  15. Medium
  16. Also uses this pattern
  17. Connections
  18. Read first
  19. Read next
  20. Check yourself
  21. References

Problem

Bit questions usually say so: count the 1 bits, reverse the bits, add two numbers without using +. Others hide it: find the one value that appears once when every other value appears twice, find the missing number with O(1) extra space, or build the smallest numbers that all share a set of bits. The giveaway is a demand for constant space on a problem that would otherwise need a hash set, or a statement phrased in terms of "AND", "OR", "XOR" or powers of two. The pattern is to reason about each bit position independently and pick the operator whose truth table does the job.

SignalPatternWhy
"Every element appears twice except one", O(1) spaceXOR everythingx ^ x = 0 and XOR is associative and commutative, so pairs cancel in any order.
"Count the 1 bits", "is it a power of two"n & (n - 1)Subtracting one flips the lowest 1 and the zeros below it, so the AND clears exactly that bit.
"Bit counts for every i from 0 to n"DP on i >> 1i has the bits of i >> 1 plus its own lowest bit, and i >> 1 is smaller, so it is already known.
"Add without + or −"XOR for the sum, AND-shift for the carryThat is one column of a hardware adder; repeat until no carry is left.
"AND of a range", "OR of all subsets"Think per bitA bit survives an AND only if it is 1 in every number, and survives an OR if it is 1 in any.
"All subsets of a small set" (n ≤ 20)Bitmasks 0 to 2n − 1Bit i of the mask says whether element i is in, so counting enumerates every subset once.

Intuition

Think of each bit position as its own tiny problem. AND keeps a bit only where both inputs have it, OR keeps it where either has it, and XOR keeps it where exactly one has it, which makes XOR "addition with no carry" and also "toggle". Because XOR undoes itself, XORing a list in which every value appears twice leaves zero in every position, and adding one extra value leaves exactly that value.

The other workhorse is what subtracting one does to a binary number: the lowest 1 becomes 0 and every 0 below it becomes 1. The bits above are untouched. So n and n - 1 agree above the lowest 1 and disagree from it downwards, and their AND is n with its lowest 1 switched off. Counting how many times you can do that before reaching zero counts the 1 bits, and the loop runs once per 1 rather than once per position.

Counting the 1 bits of 44 by clearing the lowest one each step 44 is 101100 in binary. Each row clears its lowest set bit with n AND n minus 1: 44 (101100) loses bit value 4 and becomes 40; 40 (101000) loses bit value 8 and becomes 32; 32 (100000) loses bit value 32 and becomes 0. Zero is reached after 3 steps, so 44 has 3 one bits. 32 16 8 4 2 1 n = 44 step 1 1 0 1 1 0 0 & (n − 1) → 40 n = 40 step 2 1 0 1 0 0 0 & (n − 1) → 32 n = 32 step 3 1 0 0 0 0 0 & (n − 1) → 0 n = 0 0 0 0 0 0 0 stop: 3 ones

Green bits are set; amber marks the lowest set bit, the one n & (n - 1) removes. The loop never visits the zero positions.

Mechanics

The operators and their identities

  • x ^ x == 0, x ^ 0 == x, and XOR is associative and commutative: order does not matter, pairs cancel.
  • x & (x - 1) clears the lowest set bit; it is zero exactly when x is zero or a power of two.
  • x & -x isolates the lowest set bit, because −x in two's complement is ~x + 1.
  • x >> k & 1 reads bit k; x | 1 << k sets it; x & ~(1 << k) clears it; x ^ 1 << k toggles it.
  • x >> 1 is x // 2 and x << 1 is 2 * x for non-negative x.

Cancelling pairs

Invariant for Single Number: after reading a prefix, the accumulator is the XOR of the values that have appeared an odd number of times in it. At the end only the lone value is odd. Missing Number is the same trick with a second list: XOR every index 0 to n with every value, and each present number cancels against its index, leaving the one index with no partner.

Counting bits for every i

The bits of i are the bits of i >> 1 followed by one more bit, i & 1. Since i >> 1 < i for every positive i, filling the table in increasing order means the smaller entry is always ready, which is a one-line DP over O(n) states. bits[i] = bits[i & (i - 1)] + 1 works for the same reason, dropping the lowest 1 instead of the lowest bit.

Adding with XOR and AND

In a single column, the sum bit of two bits is their XOR and the carry is their AND. Doing every column at once, a ^ b is the sum ignoring carries and (a & b) << 1 is every carry moved to the column it lands in. Their true sum equals the original a + b, so repeat with that pair until the carry is zero. Each round, the carry's lowest set bit moves at least one place left, so a 32-bit sum needs at most 32 rounds. Python complicates this: its integers have no width, so a negative carry would shift left forever. Mask every value to 32 bits with 0xFFFFFFFF, then read a result above 231 − 1 back as negative by subtracting 232.

Adding 5 and 3 with XOR and shifted AND Each round replaces a and b with a XOR b and a AND b shifted left: a = 0101, b = 0011 gives sum 0110 and carry 0010; a = 0110, b = 0010 gives sum 0100 and carry 0100; a = 0100, b = 0100 gives sum 0000 and carry 1000; a = 0000, b = 1000 gives sum 1000 and carry 0000. The carry reaches zero after 4 rounds and the answer is 8. round 1 → a 0 1 0 1 b 0 0 1 1 a^b 0 1 1 0 carry 0 0 1 0 round 2 → a 0 1 1 0 b 0 0 1 0 a^b 0 1 0 0 carry 0 1 0 0 round 3 → a 0 1 0 0 b 0 1 0 0 a^b 0 0 0 0 carry 1 0 0 0 round 4 a 0 0 0 0 b 1 0 0 0 a^b 1 0 0 0 carry 0 0 0 0 5 + 3: the carry walks left (0010, 0100, 1000, 0000) and the last sum, 1000, is 8

Each column is one round. The next round's a and b are this round's green sum and amber carry. The diagram scrolls sideways on a narrow screen.

Common prefixes and free bits

The AND of every number from left to right keeps a bit only if no number in the range has a 0 there. Below the highest bit where left and right differ, the range crosses a point where that low block rolls over from all ones to all zeros, so every lower bit is 0 somewhere. What survives is the common binary prefix: shift both right until they are equal, then shift back. Minimum Array End runs the other way. Every element must contain all of x's bits, so the valid values are x with some pattern written into its zero positions, and those patterns, read in order, are just 0, 1, 2, … Writing n − 1 into the zero bits of x gives the n-th smallest valid value.

Worked example

Single Number on [4, 1, 2, 1, 2].

StepValueAccumulatorBinary
144100
215101
327111
416110
524100

The 1s and 2s cancel and the accumulator ends at 4.

Number of 1 Bits, n = 44. The table repeats the first diagram.

StepnBinaryLowest set bit (n & −n)n & (n − 1)
144101100440
240101000832
332100000320

3 steps, so 44 has 3 one bits.

Counting Bits for 0 to 8.

iBinaryi >> 1i & 1bits[i >> 1] + (i & 1)
00000000
10001011
20010101
30011112
40100201
50101212
60110302
70111313
81000401

Sum of Two Integers, 5 + 3 and −2 + 3.

Roundaba ^ b(a & b) << 1
15362
26244
34408
40880

The answer is 8. For −2 + 3 the masked −2 is 4294967294; the carry walks all the way out of the 32-bit window in 31 rounds and the result reads back as 1.

Bitwise AND of 9 through 12.

Shiftleftrightleft (binary)right (binary)
091210011100
146100110
2231011
31111

The common prefix is 1 after 3 shifts, so the AND is 1 << 3 = 8.

Minimum Array End, n = 3, x = 4 (100). The third valid value writes n − 1 = 2 = 10 into x's zero bits: bit 0 gets 0, bit 1 gets 1, giving 110 = 6, so the array is 4, 5, 6. Two smaller checks: Missing Number on [3, 0, 1] gives 2, and Add Binary on "1011" and "111" gives "10010".

Implementation

def single_number(nums):
    acc = 0
    for value in nums:
        acc ^= value
    return acc


def hamming_weight(n):
    count = 0
    while n:
        n &= n - 1
        count += 1
    return count


def count_bits(n):
    bits = [0] * (n + 1)
    for i in range(1, n + 1):
        bits[i] = bits[i >> 1] + (i & 1)
    return bits

Variations and edge cases

MASK = 0xFFFFFFFF


def get_sum(a, b):
    a, b = a & MASK, b & MASK
    while b:
        a, b = a ^ b, (a & b) << 1 & MASK
    return a if a <= 0x7FFFFFFF else a - (1 << 32)    # read bit 31 as the sign


def reverse_bits(n):
    result = 0
    for _ in range(32):
        result = result << 1 | n & 1
        n >>= 1
    return result


def range_bitwise_and(left, right):
    shift = 0
    while left != right:
        left, right, shift = left >> 1, right >> 1, shift + 1
    return left << shift


def min_end(n, x):
    result, k, bit = x, n - 1, 1
    while k:
        if not x & bit:
            if k & 1:
                result |= bit
            k >>= 1
        bit <<= 1
    return result
  • Operator precedence. In Python, + and - bind tighter than shifts, which bind tighter than &, then ^, then |, so 1 << n - 1 is 1 << (n - 1). Comparisons bind looser than all of them, so x & 1 == 0 works in Python but not in C or Java, where it parses as x & (1 == 0). Parentheses cost nothing.
  • Negative numbers. Python's -1 >> 1 is −1 forever, and bin(-5) is '-0b101', not two's complement. Mask with 0xFFFFFFFF whenever a problem means 32-bit unsigned.
  • Fixed width. Reverse Bits must loop exactly 32 times, even after n becomes zero, or leading zeros of the input are lost from the end of the output.
  • Built-ins. int.bit_count() (Python 3.10+) and bin(n).count("1") count bits; n.bit_length() gives the position of the highest 1. Say you know them, then show the loop.

Complexity and tradeoffs

  • XOR folds are O(n) time and O(1) space; the hash-set alternative is also O(n) time but O(n) space, which is exactly what Single Number rules out.
  • Popcount with n & (n - 1) is O(number of 1 bits), at most 32 for a 32-bit integer; checking every position is always 32. Both are O(1) for fixed width.
  • Counting Bits is O(n) for the whole table, against O(n log n) for popcounting each i separately.
  • Add without plus takes at most 32 rounds, since the carry's lowest bit moves left each round.
  • Range AND is O(log right), not O(right − left), and Minimum Array End is O(log n + log x) where the naive search through supersets of x is O(n).
  • When it is the wrong tool. Missing Number is just as clear with the sum formula from number math. Bitmask enumeration of subsets is O(2n · n) and only fits n up to about 20; past that, prune with backtracking. Readability matters: a trick an interviewer has to decode is worth a sentence of explanation.

Problems

Do Single Number, Number of 1 Bits and Counting Bits first; they are the three identities in their purest form. Then Missing Number two ways, Reverse Bits for fixed-width care, and Sum of Two Integers last, because Python's unbounded integers make the masking the real lesson.

Easy

  • 67. Add Binary Grind 169 NeetCode 250
    Add the strings from right to left with a carry, the same column-by-column addition as with decimal digits but in base two.
  • 136. Single Number Grind 169 NeetCode 150 NeetCode 250
    XOR every number together: equal pairs cancel to zero, leaving the one that appears once.
  • 190. Reverse Bits Blind 75 Grind 169 NeetCode 150 NeetCode 250
    Shift bits out of the input from the right and into the result from the left, 32 times.
  • 191. Number of 1 Bits Blind 75 Grind 169 NeetCode 150 NeetCode 250
    n & (n-1) clears the lowest set bit, so count how many times that runs before n reaches zero.
  • 268. Missing Number Blind 75 Grind 169 NeetCode 150 NeetCode 250
    XOR all indices 0..n with all values, or subtract the array's sum from n(n+1)/2; either way the missing number is what is left.
  • 338. Counting Bits Blind 75 Grind 169 NeetCode 150 NeetCode 250
    The bit count of i is the count of i shifted right by one, plus the lowest bit of i.

Medium

  • 201. Bitwise AND of Numbers Range NeetCode 250
    The AND over a range keeps only the common binary prefix of its two ends, so shift both right until they are equal.
  • 371. Sum of Two Integers Blind 75 NeetCode 150 NeetCode 250
    XOR adds without carrying and AND shifted left gives the carries, so repeat until there are no carries, masking to 32 bits in Python.
  • 3133. Minimum Array End NeetCode 250
    Every element must contain x's set bits, so write n-1 in binary into the zero bit positions of x.

Also uses this pattern

These problems have a different home page, but this idea is part of their solution.

Connections

Read first

Read next

Bits are base-two digits, so everything on number math carries over: Add Binary is the decimal carry loop in base two, and square-and-multiply reads the exponent's bits. Bitmasks enumerate the subsets that backtracking builds by recursion, and a set packed into an integer is the state of bitmask DP. Counting Bits is a reminder that DP does not need a grid, just a smaller answer you already have.

Check yourself

Why does n & (n - 1) clear exactly the lowest set bit?

Subtracting one turns the lowest 1 into 0 and every 0 below it into 1, leaving higher bits alone. The two numbers agree above that position and disagree at and below it, so the AND keeps the high bits and zeroes the rest, which were all 0 in n except the lowest 1.

Why does XORing [4, 1, 2, 1, 2] give 4 regardless of the order?

XOR is associative and commutative, so the list can be regrouped as 4 ^ (1 ^ 1) ^ (2 ^ 2) = 4 ^ 0 ^ 0 = 4.

Why does adding with XOR and AND loop forever in Python on −2 + 3 without the mask?

Python integers are unbounded, so a carry from a negative number keeps shifting left into ever higher bits instead of falling off bit 31. Masking to 32 bits lets it fall off, and the loop ends after 31 rounds with 1.

Why is the AND of 9 through 12 equal to 8?

9 is 1001 and 12 is 1100; they share only the leading 1. The range passes 1011 to 1100, where the low three bits roll over, so each of those bits is 0 in some number. Only the common prefix survives, which is 8.

References

  • Warren, H. S., Hacker's Delight, 2nd edition, chapter 2 (basics: turning off the rightmost 1 bit, isolating it) and chapter 5 (counting bits).
  • Knuth, D. E., The Art of Computer Programming, volume 4A, section 7.1.3, "Bitwise tricks and techniques".
  • Cormen, Leiserson, Rivest and Stein, Introduction to Algorithms, 3rd edition, problem 17-1 (the bit-reversed binary counter).
  • Skiena, S., The Algorithm Design Manual, section 14.5, on generating subsets with bitmasks.
  • Sedgewick and Wayne, Algorithms, section 1.1, on primitive integer types and their ranges.
  • The NeetCode roadmap and the Blind 75, Grind 169, NeetCode 150 and NeetCode 250 lists, used as the index of topics and problems.