Bit Manipulation in C: Bitwise Operators, Tricks, Unique Elements & Subset Generation with Code Examples
1. What is Bit Manipulation?
Bit manipulation involves using bitwise operators to perform operations directly on the binary representation of numbers. It's efficient for tasks like setting, clearing, or toggling bits, and is used to optimize algorithms in terms of time and space.
2. What are the bitwise operators in C?
- AND (&): Returns 1 if both bits are 1 (e.g., 5 & 3 = 0101 & 0011 = 0001 = 1).
- OR (|): Returns 1 if at least one bit is 1 (e.g., 5 | 3 = 0101 | 0011 = 0111 = 7).
- XOR (^): Returns 1 if bits differ (e.g., 5 ^ 3 = 0101 ^ 0011 = 0110 = 6).
- NOT (~): Flips all bits (e.g., ~5 = ~0101 = 1010, depends on system).
- Left Shift (<<): Shifts bits left, fills with 0s (e.g., 5 << 1 = 0101 << 1 = 1010 = 10).
- Right Shift (>>): Shifts bits right, fills with sign bit for signed integers (e.g., 5 >> 1 = 0101 >> 1 = 0010 = 2).
3. What are common bit manipulation tricks?
- Check if a number is even/odd:
n & 1(0 for even, 1 for odd). - Set a bit:
n | (1 << i)(sets the i-th bit to 1). - Clear a bit:
n & ~(1 << i)(sets the i-th bit to 0). - Toggle a bit:
n ^ (1 << i)(flips the i-th bit). - Check if a bit is set:
(n >> i) & 1(returns 1 if the i-th bit is set). - Get lowest set bit:
n & -n(isolates the rightmost 1). - Clear all bits up to i-th bit:
n & ~((1 << (i + 1)) - 1). - Power of 2 check:
n & (n - 1) == 0(true if n is a power of 2).
4. What are the advantages of bit manipulation?
- Efficiency: Operates at the bit level, reducing time and space complexity.
- Compact Code: Solves problems with fewer operations.
- Memory Savings: Uses bits to represent states or flags.
5. What are the disadvantages of bit manipulation?
- Readability: Code can be hard to understand.
- Portability: Behavior depends on integer size and signedness.
- Debugging: Bit-level errors are tricky to trace.
6. What are the applications of bit manipulation?
- Finding unique elements in arrays.
- Generating subsets.
- Optimizing arithmetic operations (e.g., division by powers of 2).
- Bitmasking for dynamic programming or state representation.
- Solving problems like parity checks, counting set bits, or finding missing numbers.
7. How do bitwise operators work in C?
Example: Demonstrating common bitwise operations on numbers 5 (0101) and 3 (0011).
#include <stdio.h>
int main() {
int a = 5, b = 3; // a = 0101, b = 0011
printf("AND (5 & 3): %d\n", a & b); // 0001 = 1
printf("OR (5 | 3): %d\n", a | b); // 0111 = 7
printf("XOR (5 ^ 3): %d\n", a ^ b); // 0110 = 6
printf("NOT (~5): %d\n", ~a); // Depends on system (e.g., -6 for 32-bit)
printf("Left Shift (5 << 1): %d\n", a << 1); // 1010 = 10
printf("Right Shift (5 >> 1): %d\n", a >> 1); // 0010 = 2
return 0;
}
8. How to check if a number is a power of 2 using bit manipulation?
A number n is a power of 2 if it has exactly one bit set (e.g., 4 = 0100). Check using n & (n - 1) == 0.
#include <stdio.h>
#include <stdbool.h>
bool isPowerOfTwo(int n) {
if (n <= 0) return false; // Handle non-positive numbers
return (n & (n - 1)) == 0; // True if only one bit is set
}
int main() {
printf("Is 4 a power of 2? %d\n", isPowerOfTwo(4)); // Output: 1 (true)
printf("Is 6 a power of 2? %d\n", isPowerOfTwo(6)); // Output: 0 (false)
return 0;
}
9. How do you count the number of set bits (1s) in a number?
Use Brian Kernighan's algorithm: n & (n - 1) clears the lowest set bit, repeat until n becomes 0.
#include <stdio.h>
int countSetBits(int n) {
int count = 0;
while (n) {
n &= (n - 1); // Clear the lowest set bit
count++;
}
return count;
}
int main() {
printf("Set bits in 9 (1001): %d\n", countSetBits(9)); // Output: 2
return 0;
}
10. What is the Unique Elements problem?
Given an array where all elements appear an even number of times except one (or a few) elements that appear an odd number of times, find the unique element(s). Bit manipulation leverages XOR's property: a ^ a = 0 and a ^ 0 = a.
11. How does bit manipulation help find a single unique element?
XOR all elements; pairs cancel out (become 0), leaving the unique element.
#include <stdio.h>
int findUnique(int arr[], int n) {
if (n == 0) return -1; // Handle empty array
int result = 0;
for (int i = 0; i < n; i++) {
result ^= arr[i]; // XOR cancels pairs
}
return result;
}
int main() {
int arr[] = {7, 3, 5, 3, 5, 7, 4};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Unique element: %d\n", findUnique(arr, n)); // Output: 4
return 0;
}
12. How do you find two unique elements when all others appear twice?
- XOR all elements to get
x ^ y(where x, y are unique). - Find a set bit in the result (e.g., rightmost:
result & -result). - Divide elements into two groups based on that bit and XOR each group to find x and y.
#include <stdio.h>
void findTwoUnique(int arr[], int n, int* x, int* y) {
if (n < 2) {
*x = *y = -1; // Handle invalid input
return;
}
int xor_all = 0;
for (int i = 0; i < n; i++) xor_all ^= arr[i]; // x ^ y
int set_bit = xor_all & -xor_all; // Rightmost set bit
*x = 0, *y = 0;
for (int i = 0; i < n; i++) {
if (arr[i] & set_bit) *x ^= arr[i]; // Group with bit set
else *y ^= arr[i]; // Group with bit unset
}
}
int main() {
int arr[] = {2, 4, 7, 9, 2, 4};
int n = sizeof(arr) / sizeof(arr[0]);
int x, y;
findTwoUnique(arr, n, &x, &y);
printf("Two unique elements: %d %d\n", x, y); // Output: 7 9 (or 9 7)
return 0;
}
13. What is the time complexity of finding unique elements using bit manipulation?
- Single Unique:
O(n)(one pass with XOR). - Two Unique:
O(n)(two passes). - Space Complexity:
O(1).
14. What is the Subsets problem?
Given a set of distinct elements, generate all possible subsets (e.g., for {1, 2}, subsets are {}, {1}, {2}, {1, 2}). Bit manipulation uses binary numbers to represent subset choices.
15. How does bit manipulation help generate subsets?
For a set of n elements, use numbers from 0 to 2^n - 1, where each bit represents whether an element is included (1) or not (0). Example: For {1, 2}, 00 = {}, 01 = {2}, 10 = {1}, 11 = {1, 2}.
16. Can you give an example of generating subsets using bit manipulation in C?
Example: Generating all subsets for {1, 2}.
#include <stdio.h>
void generateSubsets(int arr[], int n) {
if (n == 0) {
printf("{ }\n");
return;
}
for (int i = 0; i < (1 << n); i++) { // 0 to 2^n - 1
printf("{ ");
for (int j = 0; j < n; j++) {
if (i & (1 << j)) { // Check if j-th bit is set
printf("%d ", arr[j]);
}
}
printf("}\n");
}
}
int main() {
int arr[] = {1, 2};
int n = sizeof(arr) / sizeof(arr[0]);
printf("All subsets:\n");
generateSubsets(arr, n); // Output: { }, { 1 }, { 2 }, { 1 2 }
return 0;
}
17. What is the time complexity of generating subsets using bit manipulation?
- Time Complexity:
O(n * 2^n)(2^n subsets, O(n) to process each). - Space Complexity:
O(1)(excluding output storage).
18. Why use bit manipulation for subsets instead of backtracking?
- Bit Manipulation: Simpler, more concise, avoids recursion overhead.
- Backtracking: More flexible for constrained subset problems but more complex.
19. What are other common applications of bit manipulation?
- Bitmasking in dynamic programming (e.g., subsets in TSP).
- Finding missing numbers in a sequence.
- Power set operations (e.g., union/intersection using bits).
- Optimizing arithmetic (e.g.,
n >> 1for n/2). - Checking palindromic bits (e.g., using XOR).
20. Can you give an example of finding a missing number using bit manipulation?
Problem: Find the missing number in an array of 0 to n.
#include <stdio.h>
int findMissing(int arr[], int n) {
if (n == 0) return 0; // Handle edge case
int xor_all = 0, xor_arr = 0;
for (int i = 0; i <= n; i++) xor_all ^= i; // XOR of 0 to n
for (int i = 0; i < n; i++) xor_arr ^= arr[i]; // XOR of array
return xor_all ^ xor_arr; // Missing number
}
int main() {
int arr[] = {0, 1, 3, 4};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Missing number: %d\n", findMissing(arr, n)); // Output: 2
return 0;
}
21. What is the time complexity of finding a missing number?
- Time Complexity:
O(n)(two passes for XOR). - Space Complexity:
O(1).