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,
-5is~5 + 1.
Core Bitwise Operators
- AND (
&): Returns1only if both bits are1. Useful for checking or clearing bits. - OR (
|): Returns1if at least one of the bits is1. Useful for setting bits. - XOR (
^): Returns1if 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 << kis equivalent to multiplyingxby2^k. - Right Shift (
>>): Shifts bits to the right.x >> kis equivalent to dividingxby2^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:
x ^ x = 0: Any number XORed with itself results in zero. This means identical numbers "cancel each other out".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.
- Iterate through each bit position from 0 to 31.
- For each bit position, count how many numbers in the array have that bit set.
- If the count modulo 3 is not zero, it means the unique number has a 1 at this bit position.
- 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:
- It must be positive (
n > 0). - 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. - 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, ...).
- Check if
nis a power of two:n > 0 && (n & (n-1)) == 0. - Create a mask that has 1s only at the even bit positions. For a 32-bit integer, this mask is
01010101010101010101010101010101in binary, which is0x55555555in hexadecimal. (Because0101is5in hex). - Use the AND operator (
&) between the numbernand the mask. If the result is not zero, it means the single set bit inncoincided 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
- 136. Single Number
- 191. Number of 1 Bits
- 231. Power of Two (Hint:
n > 0 && (n & (n - 1)) == 0) - 268. Missing Number (Hint: Use the XOR trick)
Medium 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 = 0andx ^ 0 = xare powerful for problems involving duplicates or missing elements. - Bitmasking: Integers can represent sets, allowing for efficient subset generation and set operations.