Using Bit Manipulation
1. Working with Bitwise Operators
| Operator | Name | Description | Example |
|---|---|---|---|
| & | AND | Performs bitwise AND operation on each bit pair | 5 & 3 = 1 (binary: 101 & 011 = 001) |
| | | OR | Performs bitwise OR operation on each bit pair | 5 | 3 = 7 (binary: 101 | 011 = 111) |
| ^ | XOR | Performs bitwise XOR operation on each bit pair | 5 ^ 3 = 6 (binary: 101 ^ 011 = 110) |
| ~ | NOT | Flips all bits (complement) | ~5 = -6 (binary: ~101 = ...11111010) |
| << | Left Shift | Shifts bits to the left, filling with zeros | 5 << 1 = 10 (binary: 101 << 1 = 1010) |
| >> | Right Shift | Shifts bits to the right, preserving sign (arithmetic) | 5 >> 1 = 2 (binary: 101 >> 1 = 010) |
| >>> | Zero-fill Right Shift | Shifts bits to the right, filling with zeros (logical) | 5 >>> 1 = 2 (binary: 101 >>> 1 = 010) |
Example: Checking if a Number is Odd or Even
function isOdd(num) {
return (num & 1) === 1;
}
console.log(isOdd(5)); // true
console.log(isOdd(4)); // false
Example: Swapping Two Numbers Without Temporary Variable
let a = 10;
let b = 20;
a = a ^ b; // a becomes 30 (10^20)
b = a ^ b; // b becomes 10 (30^20)
a = a ^ b; // a becomes 20 (30^10)
console.log(a); // 20
console.log(b); // 10
Example: Setting a Bit at Position
function setBit(num, pos) {
return num | (1 << pos);
}
let num = 5; // binary: 101
num = setBit(num, 2); // Set bit at position 2 (0-indexed)
console.log(num); // 13 (binary: 1101)
Example: Clearing a Bit at Position
function clearBit(num, pos) {
return num & ~(1 << pos);
}
let num = 13; // binary: 1101
num = clearBit(num, 2); // Clear bit at position 2 (0-indexed)
console.log(num); // 5 (binary: 101)
2. Checking Set Bits
| Operation | Description | Example | Notes |
|---|---|---|---|
| n & (1 << i) | Checks if bit at position i is set | |
Use left shift to create mask for bit at position i |
| n & (n - 1) | Removes the rightmost set bit | |
Useful for counting set bits efficiently |
| n & (-n) | Extracts the rightmost set bit | |
Uses two's complement to isolate rightmost set bit |
| popcount(n) | Counts total number of set bits | |
Can be implemented using built-in functions or bit manipulation |
3. Setting and Clearing Bits
| Operation | Expression | Description |
|---|---|---|
| Set bit at position n | num | (1 << n) |
Turns on the bit at position n |
| Clear bit at position n | num & ~(1 << n) |
Turns off the bit at position n |
| Toggle bit at position n | num ^ (1 << n) |
Flips the bit at position n |
Example: Setting and Clearing Bits
let num = 5; // Binary: 101
console.log("Original number:", num, "(binary: " + num.toString(2) + ")");
// Set bit at position 1
num = num | (1 << 1);
console.log("After setting bit 1:", num, "(binary: " + num.toString(2) + ")");
// Clear bit at position 2
num = num & ~(1 << 2);
console.log("After clearing bit 2:", num, "(binary: " + num.toString(2) + ")");
4. Toggling Bits
Bit toggling is the process of flipping a bit from 0 to 1 or from 1 to 0. This is commonly achieved using the XOR operator (^) with a mask.
| Operation | Expression | Description |
|---|---|---|
Toggle bit at position i |
num ^ (1 << i) |
Flips the bit at index i of num |
Example: Toggling a Bit
let num = 0b1010; // Binary: 10, Decimal: 10
let i = 2; // Toggle bit at position 2
num ^= (1 << i); // Result: 0b1110 = 14
console.log(num.toString(2)); // Output: "1110"
Note: The mask
1 << i creates a number with only the i-th bit set to 1. XORing with this mask flips that specific bit.
5. Finding Single Number
Given an array where every element appears twice except for one, find the single number using XOR bit manipulation.
| Property | Description |
|---|---|
| XOR Operation | For any number n, n ^ n = 0 and n ^ 0 = n. |
Example: Find Single Number in Array
const findSingleNumber = (nums) => {
let result = 0;
for (let num of nums) {
result ^= num;
}
return result;
};
// Example usage:
const nums = [2, 3, 2, 4, 4];
console.log(findSingleNumber(nums)); // Output: 3
Note: This approach works only if all elements appear exactly twice except one.
6. Finding Two Single Numbers
Given an array where every element appears twice except for two elements that appear only once, find the two unique numbers using bit manipulation.
| Property | Description |
|---|---|
| XOR Operation | Used to cancel out duplicate numbers. XOR of all elements gives XOR of the two unique numbers. |
| Isolate Rightmost Set Bit | Find the rightmost set bit in XOR result to differentiate between two unique numbers. |
Example: Finding Two Single Numbers
function findTwoSingleNumbers(arr) {
let xor = 0;
for (let num of arr) {
xor ^= num;
}
// Isolate the rightmost set bit
let rightmostSetBit = xor & (-xor);
let num1 = 0, num2 = 0;
for (let num of arr) {
if ((num & rightmostSetBit) !== 0) {
num1 ^= num;
} else {
num2 ^= num;
}
}
return [num1, num2];
}
// Example usage:
const nums = [1, 2, 1, 3, 2, 5];
console.log(findTwoSingleNumbers(nums)); // [3, 5]
Note: The XOR operation cancels out duplicates, leaving only the XOR of the two unique numbers. By isolating a bit that is set in one number but not the other, we can partition the array into two groups and XOR each group to find the unique numbers.
7. Counting Set Bits
| Method | Description | Time Complexity | Space Complexity |
|---|---|---|---|
| Built-in Function | Use Integer.bitCount() in Java or bin(n).count('1') in Python |
O(1) | O(1) |
| Brian Kernighan's Algorithm | Repeatedly clear the least significant set bit using n & (n - 1) |
O(k), where k is the number of set bits | O(1) |
| Lookup Table | Precompute counts for all 8-bit values and use them to count bits in larger numbers | O(1) for 8-bit chunks | O(256) = O(1) |
Example: Brian Kernighan's Algorithm
public static int countSetBits(int n) {
int count = 0;
while (n != 0) {
n = n & (n - 1);
count++;
}
return count;
}
Note: The built-in function is usually the fastest and most readable for practical use.
8. Checking Power of Two
Use bit manipulation to efficiently check if a number is a power of two.
| Condition | Description | Implementation |
|---|---|---|
| n & (n - 1) == 0 | For a power of two, only one bit is set. Subtracting 1 flips all bits after the set bit. Bitwise AND of n and (n-1) will be zero. | |
| n & (n - 1) == 0 and n > 0 | Ensures that n is positive, as negative numbers and zero are not powers of two. | |
9. Reversing Bits
| Operation | Description | Example |
|---|---|---|
| Reverse bits of a 32-bit integer | Reverses the order of all 32 bits in an integer. | |
| Reverse bits in groups of 4 (nibbles) | Reverses the order of nibbles (4-bit chunks) in a 32-bit integer. | |
Example: Reversing Bits of a Number
const num = 0b11001010; // Binary: 11001010
const reversed = reverseBits(num);
console.log(reversed.toString(2)); // Output: 01010011
10. Finding Missing Number
| Approach | Description | Time Complexity | Space Complexity |
|---|---|---|---|
| XOR | Use XOR properties: a ^ a = 0, a ^ 0 = a. XOR all indices and values, then XOR with n to find missing number. |
O(n) | O(1) |
| Mathematical Sum | Calculate expected sum of 0 to n, subtract actual sum to find missing number. | O(n) | O(1) |
Example: XOR Approach
function findMissingXOR(nums) {
let xor = 0;
const n = nums.length;
for (let i = 0; i < n; i++) {
xor ^= i ^ nums[i];
}
return xor ^ n;
}
// Example usage:
// Input: [0, 1, 3]
// Output: 2
Example: Mathematical Sum
function findMissingSum(nums) {
const n = nums.length;
const expectedSum = (n * (n + 1)) / 2;
const actualSum = nums.reduce((sum, num) => sum + num, 0);
return expectedSum - actualSum;
}
// Example usage:
// Input: [0, 1, 3]
// Output: 2
11. Generating Power Set
The power set of a set is the set of all possible subsets of that set. For a set with n elements, the power set contains 2^n subsets.
| Approach | Description | Time Complexity | Space Complexity |
|---|---|---|---|
| Bitmasking | Each subset can be represented by a bitmask of n bits, where each bit represents whether to include the corresponding element. |
O(n * 2^n) |
O(2^n) |
| Recursive | Recursively build subsets by including or excluding each element. | O(n * 2^n) |
O(2^n) |
| Iterative | Start with an empty set and iteratively add elements to existing subsets. | O(n * 2^n) |
O(2^n) |
Example: Bitmasking Approach
function generatePowerSet(nums) {
const result = [];
const n = nums.length;
for (let i = 0; i < (1 << n); i++) {
const subset = [];
for (let j = 0; j < n; j++) {
if (i & (1 << j)) {
subset.push(nums[j]);
}
}
result.push(subset);
}
return result;
}
// Example usage:
const nums = [1, 2, 3];
console.log(generatePowerSet(nums));
// Output: [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
Example: Recursive Approach
function generatePowerSetRecursive(nums) {
const result = [];
function backtrack(start, currentSubset) {
result.push([...currentSubset]);
for (let i = start; i < nums.length; i++) {
currentSubset.push(nums[i]);
backtrack(i + 1, currentSubset);
currentSubset.pop();
}
}
backtrack(0, []);
return result;
}
// Example usage:
const nums = [1, 2, 3];
console.log(generatePowerSetRecursive(nums));
// Output: [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
Note: The bitmask approach is efficient for generating all subsets, especially when the size of the set is small. It leverages bit operations to efficiently iterate through all combinations.
12. Solving Bitwise AND Range
| Property | Description | Example |
|---|---|---|
| bitwise AND | Performs bitwise AND operation on two numbers | 5 & 3 = 1 |
| range AND | AND operation on all numbers in a range [left, right] | 5 & 6 & 7 |
Example: Range AND Calculation
function rangeBitwiseAnd(left, right) {
let shift = 0;
while (left !== right) {
left >>>= 1;
right >>>= 1;
shift++;
}
return left << shift;
}
// Example usage:
console.log(rangeBitwiseAnd(5, 7)); // Output: 4
Note: The key insight is that the leftmost bits remain the same, and we can find the common prefix of
left and right.