Part VI · More Patterns Pattern 19 4 problems

Bit Manipulation

An integer is a row of switches. Three tricks cover most interview questions: XOR cancels pairs, x & (x - 1) turns off the lowest switch, and a mask is a set you can test in one step.

Bit questions look like puzzles, but they reward a short list of facts. Learn the operators, learn the three tricks, and learn how C# stores numbers. A C# int is exactly 32 bits in two's complement. That is good news. The bit answers you see in C or Java work as written. The traps are different ones: >> versus >>>, silent overflow, and shift counts that wrap.

Contents

  1. When to use
  2. Core idea
  3. Operator cheat list
  4. int vs uint, shifts, overflow, BitOperations
  5. The templates
  6. Common mistakes
  7. Single Number
  8. Number of 1 Bits
  9. Counting Bits
  10. Sum of Two Integers
  11. Recap

When to use

The trigger. The question mentions bits, binary, powers of two, or XOR. Or it bans the obvious tool: “without extra space”, “without +”, “in O(1) memory”. A small n (20 or less) with “every subset” is the other tell. Each subset fits in one integer.

Core idea

Three facts do most of the work.
XOR cancels pairs 5 1 0 1 3 0 1 1 5 1 0 1 = 3 0 1 1 the two 5s cancel to 0 x & (x - 1) drops the lowest 1 x 1 0 1 1 0 x - 1 1 0 1 0 1 x & (x-1) 1 0 1 0 0 amber: bits that - 1 flipped red: the lowest 1, now cleared a mask is a set d c b a 0 1 0 1 mask = 0b0101 = {a, c} union |, intersection & test: (mask >> i) & 1
Figure 19.1 — Pairs cancel under XOR, x & (x - 1) clears one bit, and a mask is a set of positions.

Reading the figure. Each square is one bit, highest bit on the left. Left: the red rows are a pair, and they cancel, so only the blue 3 survives. Middle: amber bits are the ones that subtracting 1 flipped. AND keeps neither, so the red bit is cleared. Right: bit i on means item i is in the set.

Two quick facts are worth having ready. x > 0 && (x & (x - 1)) == 0 means x is a power of two, because it had exactly one set bit. And x & -x isolates the lowest set bit. That is the move behind Fenwick trees. In .NET you can also call BitOperations.IsPow2(x).

Operator cheat list

Precedence. In C#, shifts bind looser than + and -. Then come comparisons, then ==, then &, ^ and |. So x & 1 == 0 parses as x & (1 == 0). That is an int & bool, a compile error. The compiler saves you here. It will not save you on 1 << n - 1, which means 1 << (n - 1). Bracket every bit expression.

int vs uint, shifts, overflow, BitOperations

The Python page spends a whole section on a 0xFFFFFFFF mask. Python integers have no width, so it fakes a 32-bit register by hand. C# needs none of that. An int already is a 32-bit register. -1 is thirty-two 1 bits. Bit 31 is the sign bit. Bits shifted past the top simply fall off. So the C# work is to pick the right type and the right shift.

int or uint

>> versus >>>

Overflow

BitOperations

System.Numerics.BitOperations wraps single CPU instructions. Name these in an interview, then write the loop if asked. Most take uint or ulong, so cast an int first. Since .NET 7 the same calls also exist on the type itself: int.PopCount(x), int.TrailingZeroCount(x), int.Log2(x).

using System.Numerics;

/// <summary>Small demos of 32-bit behaviour. Each returns a value a test can check.</summary>
public static class IntBits
{
    /// <summary>Arithmetic shift right by one: the sign bit is copied in.</summary>
    /// <param name="x">Any int.</param>
    /// <returns>x divided by 2, rounded toward minus infinity.</returns>
    /// <example>IntBits.ArithmeticShift(-8) returns -4.</example>
    public static int ArithmeticShift(int x) => x >> 1;     // 1: shift by one place

    /// <summary>Logical shift right by one with C# 11 >>>: a 0 is shifted in.</summary>
    /// <param name="x">Any int.</param>
    /// <returns>The bits of x moved right, with bit 31 cleared.</returns>
    /// <example>IntBits.LogicalShift(-8) returns 2147483644.</example>
    public static int LogicalShift(int x) => x >>> 1;      // 1: shift by one place

    /// <summary>Reinterpret the 32 bits of an int as unsigned.</summary>
    /// <param name="x">Any int.</param>
    /// <returns>The same bit pattern read as a uint.</returns>
    /// <example>IntBits.AsUnsigned(-1) returns 4294967295.</example>
    public static uint AsUnsigned(int x) => unchecked((uint)x);

    /// <summary>Add with silent 32-bit wraparound, the default for run-time ints.</summary>
    /// <param name="a">First addend.</param>
    /// <param name="b">Second addend.</param>
    /// <returns>a + b, wrapped into the int range.</returns>
    /// <example>IntBits.WrapAdd(int.MaxValue, 1) returns -2147483648.</example>
    public static int WrapAdd(int a, int b) => unchecked(a + b);

    /// <summary>Add, but throw instead of wrapping.</summary>
    /// <param name="a">First addend.</param>
    /// <param name="b">Second addend.</param>
    /// <returns>a + b when it fits in an int.</returns>
    /// <exception cref="OverflowException">The true sum does not fit.</exception>
    /// <example>IntBits.CheckedAdd(2, 3) returns 5.</example>
    public static int CheckedAdd(int a, int b) => checked(a + b);

    /// <summary>Four BitOperations answers for one value.</summary>
    /// <param name="u">The bit pattern to inspect.</param>
    /// <returns>[PopCount, LeadingZeroCount, TrailingZeroCount, Log2].</returns>
    /// <example>IntBits.Facts(0b10110u) returns [3, 27, 1, 4].</example>
    public static int[] Facts(uint u) =>
    [
        BitOperations.PopCount(u),            // how many 1 bits
        BitOperations.LeadingZeroCount(u),    // zeros above the highest 1
        BitOperations.TrailingZeroCount(u),   // zeros below the lowest 1
        BitOperations.Log2(u),                // position of the highest 1
    ];
}

Reverse Bits is the classic question that wants uint. With an int, the first >> on a negative input would copy the sign bit and corrupt the answer. With uint, every shift brings in a 0 and the result fills all 32 bits with no sign to worry about.

/// <summary>Reverse Bits: mirror the 32 bits of an unsigned integer.</summary>
public static class ReverseBits
{
    /// <summary>Bit i of the input becomes bit 31 - i of the output.</summary>
    /// <param name="n">Any 32-bit pattern.</param>
    /// <returns>The mirrored pattern.</returns>
    /// <example>ReverseBits.Reverse(1u) returns 2147483648.</example>
    public static uint Reverse(uint n)
    {
        uint result = 0;                        // no bits placed yet

        // i counts bits moved so far. 32: a uint has exactly 32 bits.
        // Invariant: result holds the low i bits of the input, mirrored.
        for (int i = 0; i < 32; i++)
        {
            // << 1 makes room on the right. n & 1 is the lowest input bit.
            result = (result << 1) | (n & 1);
            n >>= 1;                            // uint: a 0 comes in, never a sign bit
        }
        return result;
    }
}
From Python. Every & 0xFFFFFFFF line on the Python page is gone in C#. The “check bit 31 and use ~(x ^ mask)” step is gone too. An int with bit 31 set already is the negative number. If you need the unsigned view, cast with (uint). If you need to go back, cast with (int).

The templates

Template A — XOR to cancel pairs
/// <summary>Template A: XOR every value so that pairs cancel.</summary>
public static class XorAll
{
    /// <summary>XOR of every value. Values that appear an odd number of times survive.</summary>
    /// <param name="values">Any ints, negatives included.</param>
    /// <returns>The XOR of all values, or 0 for an empty array.</returns>
    /// <example>XorAll.Of([3, 5, 3]) returns 5.</example>
    public static int Of(int[] values)
    {
        // 0 is the XOR identity: 0 ^ x == x, so starting here changes nothing.
        int acc = 0;

        // value is the next number. Invariant: acc is the XOR of all values before it.
        foreach (int value in values)
        {
            acc ^= value;                       // a second copy of value cancels the first
        }
        return acc;
    }
}
Template B — walk the set bits with x & (x - 1)
using System.Numerics;

/// <summary>Template B: visit each set bit once, lowest first.</summary>
public static class SetBits
{
    /// <summary>Positions of the 1 bits in x, lowest first. Negatives work too.</summary>
    /// <param name="x">Any int. A negative is read as its 32-bit pattern.</param>
    /// <returns>Bit positions from 0 to 31, in increasing order.</returns>
    /// <example>SetBits.Positions(0b10110) returns [1, 2, 4].</example>
    public static List<int> Positions(int x)
    {
        var positions = new List<int>();

        // Invariant: x holds the set bits not yet reported. Each pass removes one.
        // The loop stops at 0: no set bits left. A fixed 32-bit int always gets there.
        while (x != 0)
        {
            int lowest = x & -x;                // -x flips all bits above the lowest 1
            // Log2 of a one-bit number is its position. (uint): Log2 takes unsigned.
            positions.Add(BitOperations.Log2((uint)lowest));
            x &= x - 1;                         // - 1 turns the lowest 1 into 0
        }
        return positions;
    }
}
x lowest = x & -x positions 4 3 2 1 0 pass 1 1 0 1 1 0 0 0 0 1 0 bit 1: [1] pass 2 1 0 1 0 0 0 0 1 0 0 bit 2: [1, 2] pass 3 1 0 0 0 0 1 0 0 0 0 bit 4: [1, 2, 4] x &= x - 1 removes the amber bit. After pass 3, x = 0 and the loop stops.
Figure 19.2 — The loop runs once per set bit: three passes for the three 1s in 10110.

Reading the figure. Each row is one pass of the while loop. The small numbers on top are bit positions. The amber bit is the lowest 1, which x & -x isolates. Its position joins the green list. Then x &= x - 1 clears it, giving the next row.

The loop runs once per set bit, not once per bit position. On a sparse number that beats checking all 32 positions. BitOperations.TrailingZeroCount(x) gives the same position in one call. On int.MinValue, -x wraps back to int.MinValue, and the code still reports bit 31.

Template C — masks as sets: visit every subset
/// <summary>Template C: one int mask per subset.</summary>
public static class SubsetsByMask
{
    /// <summary>Every subset of items. Bit i of mask means items[i] is in.</summary>
    /// <param name="items">Up to about 20 items, since there are 2 to the n subsets.</param>
    /// <returns>All subsets, in mask order 0, 1, 2 and so on.</returns>
    /// <example>SubsetsByMask.All(["a", "b"]) returns [[], [a], [b], [a, b]].</example>
    public static List<List<string>> All(string[] items)
    {
        int n = items.Length;
        var result = new List<List<string>>();

        // 1 << n is 2 to the n: one mask per subset, from 0 (empty) to (1 << n) - 1 (all).
        // n must stay below 31, or 1 << n overflows the int.
        // Invariant: every mask below this one has already been added.
        for (int mask = 0; mask < (1 << n); mask++)
        {
            var subset = new List<string>();
            // i is the item index, 0..n-1. Invariant: subset has the chosen items before i.
            for (int i = 0; i < n; i++)
            {
                // (mask >> i) & 1 reads bit i. The 1 keeps only that bit. == 1: it is on.
                if (((mask >> i) & 1) == 1)
                {
                    subset.Add(items[i]);
                }
            }
            result.Add(subset);
        }
        return result;
    }
}
items = [a, b, c]. Bit 0 is a, bit 1 is b, bit 2 is c. mask = 0 c b a 0 0 0 {} mask = 1 c b a 0 0 1 {a} mask = 2 c b a 0 1 0 {b} mask = 3 c b a 0 1 1 {a, b} mask = 4 c b a 1 0 0 {c} mask = 5 c b a 1 0 1 {a, c} mask = 6 c b a 1 1 0 {b, c} mask = 7 c b a 1 1 1 {a, b, c}
Figure 19.3 — Counting mask from 0 to (1 << n) - 1 visits every subset exactly once.

Reading the figure. Each card is one value of mask. The letters above the bits say which item each bit stands for. Green bits are on, so those items are in the subset on the right. Notice that the masks are just the numbers 0 to 7 in order, and together they cover all 8 subsets.

This is the iterative twin of backtracking subsets. It only works while 1 << n is small, so about n ≤ 20. Past 30 the int mask itself overflows. Use long and 1L << n for up to 62.

Common mistakes

The problems

1. Single Number Easy

Problem

Every value in a non-empty array appears exactly twice, except one value that appears once. Find it in O(n) time and O(1) extra space.

The idea

A HashSet<int> works but costs O(n) space. XOR is the O(1) answer. XOR is commutative and associative, so the order does not matter. Each pair meets itself and becomes 0. The single value XOR 0 is itself.

Solution

/// <summary>Single Number: every value appears twice except one.</summary>
public static class SingleNumber
{
    /// <summary>The one value that appears once when every other appears twice.</summary>
    /// <param name="nums">Non-empty ints. Exactly one value appears once.</param>
    /// <returns>The value that appears once.</returns>
    /// <example>SingleNumber.Find([4, 1, 2, 1, 2]) returns 4.</example>
    public static int Find(int[] nums)
    {
        // Start at 0: XOR with 0 changes nothing, so 0 is the neutral start.
        int result = 0;

        // value is the next number. Invariant: result is the XOR of every number
        // seen so far. Pairs seen twice have cancelled to 0.
        foreach (int value in nums)
        {
            result ^= value;
        }
        return result;
    }
}

Walkthrough

nums = [4, 1, 2, 1, 2], in binary:

step 4 2 1 result after the step start 0 0 0 ^ 4 1 0 0 4 goes in ^ 1 1 0 1 1 goes in ^ 2 1 1 1 2 goes in ^ 1 1 1 0 second 1 cancels the first ^ 2 1 0 0 second 2 cancels: answer 4
Figure 19.4 — Each repeated value switches its bit on and then off, so only 4 is left.

Reading the figure. Each row is result after one XOR, with place values 4, 2, 1 on top. Blue squares are 1 bits. A red square is a bit that a second copy just switched back to 0. The green row is the answer.

TimeO(n)SpaceO(1)

Edge cases to raise

Say this out loud: “XOR is order-free and every pair cancels to zero, so XOR-ing the whole array leaves only the single value. One pass, one integer of space.”

2. Number of 1 Bits Easy

Problem

Return the number of 1 bits in the 32-bit binary form of an integer. This count is also called the Hamming weight.

The idea

Checking all 32 positions works. The better answer uses n & (n - 1), which clears one set bit per pass. The loop runs exactly as many times as there are 1s. A negative int needs no special case in C#. Its 32-bit pattern is what gets counted, and the loop still ends.

Solution

/// <summary>Number of 1 Bits (Hamming weight).</summary>
public static class HammingWeight
{
    /// <summary>Number of 1 bits in the 32-bit two's complement form of n.</summary>
    /// <param name="n">Any int. A negative is read as its 32-bit pattern.</param>
    /// <returns>The count of set bits, from 0 to 32.</returns>
    /// <example>HammingWeight.Count(11) returns 3. HammingWeight.Count(-1) returns 32.</example>
    public static int Count(int n)
    {
        int count = 0;                          // no set bits counted yet

        // Invariant: count + (set bits left in n) == the answer.
        // The loop stops at 0: no set bits left. For -1 it takes 32 passes:
        // -1, -2, -4, ... int.MinValue, then int.MinValue - 1 wraps and the AND gives 0.
        while (n != 0)
        {
            n &= n - 1;                         // - 1 flips the lowest 1 to 0, AND clears it
            count++;                            // one bit removed, so one more counted
        }
        return count;
    }
}

Walkthrough

n = 11, binary 1011:

pass 1: count 1 n 1 0 1 1 n - 1 1 0 1 0 n & (n-1) 1 0 1 0 pass 2: count 2 n 1 0 1 0 n - 1 1 0 0 1 n & (n-1) 1 0 0 0 pass 3: count 3 n 1 0 0 0 n - 1 0 1 1 1 n & (n-1) 0 0 0 0 Each pass clears the amber bit, shown red after the AND. n = 0 after pass 3, so the loop stops. 1011 has three 1s.
Figure 19.5 — n & (n - 1) clears exactly one 1 per pass, so the pass count is the answer.

Reading the figure. Each block is one pass. The amber bit is the lowest 1 of n. Subtracting 1 flips it and every 0 below it. After the AND, that bit is the red square, and the green row is the new n. Three passes means three 1 bits.

TimeO(set bits), at most 32SpaceO(1)

Edge cases to raise

Say this out loud: “n & (n - 1) clears the lowest set bit, so the loop runs once per 1 bit. A C# int is 32 bits, so a negative input just has more 1s, and the loop still ends. In real code I would call BitOperations.PopCount.”

3. Counting Bits Easy

Problem

Given n, return an array ans of length n + 1 where ans[i] is the number of 1 bits in i. Aim for O(n), not O(n log n).

The idea

This is dynamic programming on bits. Shifting i right by one drops its last bit and gives i >> 1. That is a smaller number whose count is already known. The dropped bit is i & 1. So ans[i] = ans[i >> 1] + (i & 1). Each cell costs O(1).

Solution

/// <summary>Counting Bits: set-bit counts for 0..n as a DP table.</summary>
public static class CountingBits
{
    /// <summary>Set-bit counts for every integer from 0 to n.</summary>
    /// <param name="n">The largest integer to count, n >= 0.</param>
    /// <returns>An array of length n + 1. Entry i is the number of 1 bits in i.</returns>
    /// <exception cref="ArgumentOutOfRangeException">n is negative.</exception>
    /// <example>CountingBits.Count(5) returns [0, 1, 1, 2, 1, 2].</example>
    public static int[] Count(int n)
    {
        ArgumentOutOfRangeException.ThrowIfNegative(n);

        // n + 1 slots so index n exists: slots 0..n. Slot 0 stays 0, the base
        // case, because 0 has no set bits. C# arrays start zeroed.
        var ones = new int[n + 1];

        // Start at 1: slot 0 is the base case. <= n so n itself is included.
        // Invariant: ones[0..i-1] are final. i >> 1 < i, so it is ready.
        for (int i = 1; i <= n; i++)
        {
            // i >> 1 drops the last bit (shift by 1 = halve). i & 1 is that
            // dropped bit: 1 if i is odd, 0 if even.
            ones[i] = ones[i >> 1] + (i & 1);
        }
        return ones;
    }
}

Walkthrough

i binary ones[i] 0 0 0 1 1 1 2 10 1 3 11 2 4 100 1 5 101 2 ones[i] = ones[i >> 1] + (i & 1). Amber: the last bit, i & 1.
Figure 19.6 — Each count reuses the count of i >> 1 and adds the one bit that the shift dropped.

Reading the figure. Each column is one i. Blue arrows point from i back to i >> 1, a smaller cell that is already filled. The amber digit is the last bit, the one the shift drops. So each green count is the count at the arrow tip plus the amber digit.

TimeO(n)SpaceO(n) for the output

Edge cases to raise

Say this out loud: “Shifting right by one gives a smaller number I have already solved, and the bit I dropped is i & 1. So each entry is one lookup plus one bit. That makes it a DP over the integers.”

4. Sum of Two Integers Medium

Problem

Return a + b without using the + or - operators. Inputs are 32-bit signed integers, and negatives must work.

The idea

Binary addition splits into two parts. a ^ b is the sum of each column ignoring carries. (a & b) << 1 is the carries, each moved one column left. Add those two the same way, and repeat until no carry is left. In C# this ends in at most 32 rounds. Each round moves the carry one column left, and a carry out of bit 31 falls off the top. Negatives need no extra code, because two's complement addition is the same bit work for every sign.

Solution

/// <summary>Sum of Two Integers without + or -.</summary>
public static class SumOfTwo
{
    /// <summary>a + b using only bit operations, with 32-bit signed wraparound.</summary>
    /// <param name="a">A 32-bit signed integer.</param>
    /// <param name="b">A 32-bit signed integer.</param>
    /// <returns>Their sum, wrapped like unchecked int addition.</returns>
    /// <example>SumOfTwo.GetSum(-2, 3) returns 1.</example>
    public static int GetSum(int a, int b)
    {
        // Invariant: a + b (as 32-bit ints) equals the true answer.
        // b is the carry. Its lowest 1 moves at least 1 column left each pass,
        // so after at most 32 passes it falls off bit 31 and b becomes 0.
        while (b != 0)
        {
            // Columns where both are 1 make a carry. << 1 moves it to the next
            // column. A carry out of bit 31 is dropped, like real hardware.
            int carry = (a & b) << 1;
            a ^= b;                             // column sums with no carry
            b = carry;                          // the carries still to add
        }
        return a;
    }
}

Walkthrough

a = 5 (0101), b = 3 (0011):

a = a ^ b (sum, no carry) b = (a & b) << 1 (carry) start 0 1 0 1 0 0 1 1 5 + 3 pass 1 0 1 1 0 0 0 1 0 carry moves left pass 2 0 1 0 0 0 1 0 0 pass 3 0 0 0 0 1 0 0 0 pass 4 1 0 0 0 0 0 0 0 b = 0: answer 8
Figure 19.7 — The carry walks one column left each pass until it is 0, and a holds the sum.

Reading the figure. Each row shows a and b after one pass. Blue bits are the sum so far, without carries. Amber bits are the carries, already moved one column left. Watch the single amber 1 climb from column 1 to column 3. When b is all zeros, the green a is the answer.

TimeO(32) = O(1)SpaceO(1)

Edge cases to raise

Say this out loud: “XOR adds each column without carries, and AND shifted left gives the carries. I repeat until the carry is zero. A C# int is 32 bits, so a carry out of bit 31 falls off and negatives just work. No mask is needed.”

Recap

The six things to carry forward

Where this goes next

Pattern 20, K-way Merge and Two Heaps, goes back to heaps. It covers the two heap moves the top-K page only names: merging many sorted lists, and keeping a running median. In C# both use PriorityQueue<TElement, TPriority>. The Python version of this page is here.


← 18 — Trie 20 — K-way Merge and Two Heaps →