Mastering Bit Manipulation

From Basics to Advanced DSA for Competitive Programming and Interviews.


Part 1: The Basics

What is Bit Manipulation?

Bit Manipulation is a technique that uses bitwise operators to work directly on the binary representation (bits) of numbers. It's the art of talking to computers in their native language: 0s and 1s.

Why is it important?

  • It allows for highly optimised solutions in terms of speed and memory.
  • Many operations can be made more efficient by accessing data at the bit level.
  • It's a key topic in competitive programming and for optimising low-level code.

How Computers Store Numbers

All data in computers is stored internally as bits (0s and 1s). An n-bit integer is a binary number consisting of n bits.

  • Signed vs. Unsigned: Signed numbers use the first bit as a sign bit (0 for non-negative, 1 for negative). Unsigned numbers are only non-negative.
  • Two's Complement: The standard way to represent negative numbers. It's calculated by inverting all bits and then adding one. For example, -5 is ~5 + 1.

Core Bitwise Operators

  • AND (&): Returns 1 only if both bits are 1. Useful for checking or clearing bits.
  • OR (|): Returns 1 if at least one of the bits is 1. Useful for setting bits.
  • XOR (^): Returns 1 if the bits are different. Useful for flipping bits and finding unique elements.
  • NOT (~): Inverts all bits of the number (1s become 0s and 0s become 1s).

Shift Operators

  • Left Shift (<<): Shifts bits to the left, filling with zeros. x << k is equivalent to multiplying x by 2^k.
  • Right Shift (>>): Shifts bits to the right. x >> k is equivalent to dividing x by 2^k (integer division).

Part 2: Common Tricks & Applications

Manipulating Single Bits

Here are the four fundamental operations on the k-th bit of a number num (0-indexed from the right).

  • Check/Get the k-th bit: Use (num & (1 << k)) != 0. This isolates the k-th bit. If the result is non-zero, the bit was 1.
  • Set the k-th bit: Use num = num | (1 << k). The OR operation sets the bit without changing others.
  • Clear the k-th bit: Use num = num & ~(1 << k). This creates a mask with only the k-th bit as 0, then uses AND to clear it.
  • Flip the k-th bit: Use num = num ^ (1 << k). The XOR operation inverts the k-th bit.

The Famous XOR Trick

The XOR operator has two crucial properties for problem-solving:

  1. x ^ x = 0: Any number XORed with itself results in zero. This means identical numbers "cancel each other out".
  2. x ^ 0 = x: Any number XORed with zero remains unchanged.

This trick means in a sequence like a ^ b ^ c ^ a ^ b, you can remove all pairs of duplicated values, simplifying it to just c.

Application: Find the Single Number (LeetCode 136)

Problem: Given an array where every element appears twice except for one, find that single element in O(n) time and O(1) space.

Solution: XOR all the numbers in the array. All the duplicate pairs will cancel out to 0, leaving only the unique number.


class SingleNumberSolution {
    public int singleNumber(int[] nums) {
        int result = 0;
        for (int num : nums) {
            result = result ^ num;
        }
        return result;
    }

    public static void main(String[] args) {
        SingleNumberSolution solution = new SingleNumberSolution();
        int[] arr = {4, 1, 2, 1, 2};
        int unique = solution.singleNumber(arr);
        // result = 0 ^ 4 ^ 1 ^ 2 ^ 1 ^ 2
        // result = 4 ^ (1 ^ 1) ^ (2 ^ 2)
        // result = 4 ^ 0 ^ 0 = 4
        System.out.println("The single number is: " + unique); // Expected: 4
    }
}
                

Application: Swapping Two Numbers In-Place

Problem: Swap the values of two variables, x and y, without using a temporary variable.


class SwapSolution {
    public static void main(String[] args) {
        int x = 10; // Binary: 1010
        int y = 5;  // Binary: 0101
        
        System.out.println("Before swap: x = " + x + ", y = " + y);

        // 1. x becomes x ^ y
        x = x ^ y; // x = 10 ^ 5 = 15 (Binary: 1111)

        // 2. y becomes y ^ (new x), which simplifies to the original x
        y = y ^ x; // y = 5 ^ 15 = 10 (Binary: 1010)

        // 3. x becomes (new x) ^ (new y), which simplifies to the original y
        x = x ^ y; // x = 15 ^ 10 = 5 (Binary: 0101)

        System.out.println("After swap: x = " + x + ", y = " + y); // Expected: x=5, y=10
    }
}
                

Part 3: Beginner Friendly Problems

Problem: Single Number II (LeetCode 137)

Problem: You are given an array where every number appears exactly 3 times, except for one number which appears only once. Your task is to find that unique number in O(n) time and O(1) space.

Approach: Bit Counting

The core idea is to analyze the bits of all the numbers. If we consider a single bit position (e.g., the 0th bit), the total count of set bits (1s) at that position must be a multiple of 3 if the unique number has a 0 there. If the unique number has a 1 at that position, the total count will not be a multiple of 3. We can use this property to reconstruct the unique number bit by bit.

  1. Iterate through each bit position from 0 to 31.
  2. For each bit position, count how many numbers in the array have that bit set.
  3. If the count modulo 3 is not zero, it means the unique number has a 1 at this bit position.
  4. Set the corresponding bit in our answer variable. After checking all 32 bits, the answer variable will hold the unique number.

class SingleNumberThreeTimesSolution {
    public int singleNumber(int[] nums) {
        int ans = 0;
        // Iterate through each bit position (0 to 31 for a 32-bit integer)
        for (int i = 0; i < 32; i++) {
            int sum = 0;
            // For each bit, count how many numbers in the array have it set
            for (int num : nums) {
                if (((num >> i) & 1) == 1) {
                    sum++;
                }
            }
            // If the sum is not a multiple of 3, the unique number has this bit set
            if (sum % 3 != 0) {
                ans |= (1 << i);
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        SingleNumberThreeTimesSolution solution = new SingleNumberThreeTimesSolution();
        int[] arr = {2, 2, 3, 2}; // 2 (010), 3 (011)
        System.out.println("The unique number is: " + solution.singleNumber(arr)); // Expected: 3
        
        int[] arr2 = {0, 1, 0, 1, 0, 1, 99};
        System.out.println("The unique number is: " + solution.singleNumber(arr2)); // Expected: 99
    }
}
                

Problem: Check if a number is a power of 4

Problem: Given a 32-bit signed integer, write a function to determine if it is a power of four.

Approach 1 (Mathematical Property): A number n is a power of 4 if it meets three conditions:

  1. It must be positive (n > 0).
  2. It must be a power of two. We can check this with (n & (n - 1)) == 0. This works because a power of two has only one bit set (e.g., 8 is 1000), and subtracting one flips all bits to the right of it (e.g., 7 is 0111). ANDing them gives zero.
  3. The single set bit must be in an "odd" position (0th, 2nd, 4th, etc. from the right). An interesting property of powers of 4 is that (n-1) is always divisible by 3. For example, 4-1=3, 16-1=15, 64-1=63.

class PowerOfFourSolution {
    public boolean isPowerOfFour(int n) {
        // Condition 1: Must be positive
        // Condition 2: Must be a power of two
        // Condition 3: (n-1) must be divisible by 3
        return n > 0 && (n & (n - 1)) == 0 && (n - 1) % 3 == 0;
    }

    public static void main(String[] args) {
        PowerOfFourSolution solution = new PowerOfFourSolution();
        System.out.println("Is 16 a power of four? " + solution.isPowerOfFour(16)); // true
        System.out.println("Is 8 a power of four? " + solution.isPowerOfFour(8));   // false
        System.out.println("Is 1 a power of four? " + solution.isPowerOfFour(1));   // true
    }
}
                

Approach 2 (Bitmask)

This approach also first verifies that the number is a power of two, which ensures only one bit is set. Then, it uses a bitmask to check if that single bit is in one of the positions that a power of four can occupy (0, 2, 4, 6, ...).

  1. Check if n is a power of two: n > 0 && (n & (n-1)) == 0.
  2. Create a mask that has 1s only at the even bit positions. For a 32-bit integer, this mask is 01010101010101010101010101010101 in binary, which is 0x55555555 in hexadecimal. (Because 0101 is 5 in hex).
  3. Use the AND operator (&) between the number n and the mask. If the result is not zero, it means the single set bit in n coincided with a set bit in the mask, proving it's at an even position.

class PowerOfFourSolutionAlt {
    public boolean isPowerOfFour(int n) {
        // First, check if it's a power of two (only one bit is set).
        if (n <= 0 || (n & (n - 1)) != 0) {
            return false;
        }
        
        // Now, check if the single set bit is at an even position.
        // The mask 0x55555555 has 1s at all even positions (0, 2, 4,...).
        // For a power of 4, its single bit MUST be at an even position.
        // ANDing n with the mask will be non-zero if the bit is at an even position.
        return (n & 0x55555555) != 0;
    }

    public static void main(String[] args) {
        PowerOfFourSolutionAlt solution = new PowerOfFourSolutionAlt();
        System.out.println("Is 16 a power of four? " + solution.isPowerOfFour(16)); // true
        System.out.println("Is 8 a power of four? " + solution.isPowerOfFour(8));   // false
        System.out.println("Is 1 a power of four? " + solution.isPowerOfFour(1));   // true
    }
}
                

Part 4: Advanced Topics

Representing Sets with Bitmasks

A subset of {0, 1, ..., n-1} can be represented as an n-bit integer, called a bitmask. If the i-th bit is 1, the element i is in the set. If the bit is 0, it is not.

For example, with n=5, the set {0, 3, 4} is represented by the binary number 11001 (25 in decimal).

Set Operations using Bitwise Logic

  • Union (A ∪ B): A | B
  • Intersection (A ∩ B): A & B
  • Difference (A \ B): A & (~B)

Application: Generating All Subsets (LeetCode 78)

Problem: Given an array of unique elements, return all possible subsets (the power set).

Approach: An array of size n has 2^n subsets. We can loop from 0 to 2^n - 1. Each number i in this range is a bitmask that represents one unique subset. This is a highly efficient and standard bit manipulation technique for this problem.


import java.util.ArrayList;
import java.util.List;

class SubsetsSolution {
    public List<List<Integer>> subsets(int[] nums) {
        int n = nums.length;
        int numSubsets = 1 << n; // 2^n
        List<List<Integer>> powerSet = new ArrayList<>();

        // Loop from 0 to 2^n - 1
        for (int i = 0; i < numSubsets; i++) {
            List<Integer> currentSubset = new ArrayList<>();
            // Check each bit of the mask 'i'
            for (int j = 0; j < n; j++) {
                if ((i & (1 << j)) != 0) {
                    currentSubset.add(nums[j]);
                }
            }
            powerSet.add(currentSubset);
        }
        return powerSet;
    }

    public static void main(String[] args) {
        SubsetsSolution solution = new SubsetsSolution();
        int[] arr = {1, 2, 3};
        List<List<Integer>> result = solution.subsets(arr);

        System.out.println("All subsets of {1, 2, 3}:");
        for (List<Integer> subset : result) {
            System.out.println(subset);
        }
    }
}
                

Part 5: Practice Problems

Easy Problems


Part 6: Conclusion

Recap & Summary

  • The Basics: Key operators are &, |, ^, ~, <<, >>.
  • Core Techniques: You can get, set, clear, and flip individual bits using simple operations.
  • The XOR Trick: The properties x ^ x = 0 and x ^ 0 = x are powerful for problems involving duplicates or missing elements.
  • Bitmasking: Integers can represent sets, allowing for efficient subset generation and set operations.