Bitwise Operations
1. Checking if a Number is Even
The function isEven checks whether a number n is even using a bitwise AND
operation:
const isEven = (n: number): boolean => (n & 1) === 0;Explanation
Bitwise AND with 1:
- The least significant bit (LSB) of a number determines if it is even or odd.
- If the LSB is
0, the number is even. - If the LSB is
1, the number is odd.
Operation:
n & 1isolates the LSB.- If the result is
0, the number is even.
Comparison:
(n & 1) === 0evaluates totruefor even numbers andfalsefor odd numbers.
Comparison with Modulus Approach
Bitwise Approach:
(n & 1) === 0- Faster and more efficient because bitwise operations are computationally cheaper.
- Directly checks the LSB without performing division or remainder operations.
Modulus Approach:
n % 2 === 0- Easier to read and understand for beginners.
- Slightly slower due to the division/remainder operation.
Example
console.log(isEven(4)); // true (4 is even)
console.log(isEven(5)); // false (5 is odd)2. Checking if a Number is a Power of Two
The function isPowerOfTwo checks whether a number x is a power of two using
bitwise operations:
const isPowerOfTwo = (x: number): boolean => x !== 0 && !(x & (x - 1));Explanation
Power of Two Property:
- A power of two in binary has exactly one bit set (e.g.,
1->001,2->010,4->100).
- A power of two in binary has exactly one bit set (e.g.,
Bitwise Trick:
- Subtracting
1from a power of two flips all bits after the single set bit (e.g.,4 - 1 = 3->100 & 011 = 0). - For non-powers of two,
x & (x - 1)will not be zero (e.g.,5 & 4 = 4).
- Subtracting
Operation:
x & (x - 1)clears the lowest set bit inx.- If the result is
0andxis not0, thenxis a power of two.
Edge Case:
x !== 0ensures0is not considered a power of two.
Comparison with Logarithmic Approach
Bitwise Approach:
x !== 0 && !(x & (x - 1))- Extremely fast and efficient, leveraging bitwise properties.
- Avoids floating-point inaccuracies or expensive logarithmic calculations.
Logarithmic Approach:
Math.log2(x) % 1 === 0- Easier to understand conceptually.
- Slower and prone to floating-point precision issues.
Example
console.log(isPowerOfTwo(8)); // true (8 is 2**3)
console.log(isPowerOfTwo(5)); // false (5 is not a power of two)
console.log(isPowerOfTwo(0)); // false (0 is not a power of two)3. Accessing and Manipulating the Bit
Accessing the Bit
To access the x, you can use the following formula:
const bitLen = (n: number): number => {
let length: number = 0;
while (n > 0) {
n >>= 1;
length++;
}
return length;
};
const accessBit = (x: number, n: number): number => (x >> (n - 1)) & 1;Explanation:
bitLen(x)calculates the total number of bits inx.(x >> (bitLen(x) - n))shifts thebit to the least significant position. & 1isolates the bit.
Example:
const x = 0b1010; // Binary 10 (1010)
console.log(accessBit(x, 2)); // Output: 1 (second bit from the right)Toggling the Bit
To toggle the x, use the XOR (^) operator:
const toggleBit = (x: number, n: number): number => x ^ (1 << (n - 1));Explanation:
1 << (n - 1)creates a mask with a1at theposition. x ^ maskflips thebit (0 -> 1 or 1 -> 0).
Example:
const x = 0b1010; // Binary 10 (1010)
console.log(toggleBit(x, 3).toString(2)); // Output: 1110 (toggles the third bit)Setting the Bit**
To set the x to 1, use the OR (|) operator:
const setBit = (x: number, n: number): number => x | (1 << (n - 1));Explanation:
1 << (n - 1)creates a mask with a1at theposition. x | maskensures thebit is set to 1.
Example:
const x = 0b1010; // Binary 10 (1010)
console.log(setBit(x, 2).toString(2)); // Output: 1010 (no change, bit already set)
console.log(setBit(x, 3).toString(2)); // Output: 1110 (sets the third bit)Unsetting the Bit**
To unset the x (set it to 0), use the AND (&)
operator with a negated mask:
const unsetBit = (x: number, n: number): number => x & ~(1 << (n - 1));Explanation:
1 << (n - 1)creates a mask with a1at theposition. ~maskinverts the mask, making thebit 0and all others1.x & ~maskensures thebit is set to 0.
Example:
const x = 0b1010; // Binary 10 (1010)
console.log(unsetBit(x, 2).toString(2)); // Output: 1000 (unsets the second bit)
console.log(unsetBit(x, 3).toString(2)); // Output: 1010 (no change, bit already unset)4. Calculating the Bit Length of a Number
The function bitLen calculates the number of bits required to represent a
number n in binary. Here's how it works:
const bitLen = (n: number): number => {
let length: number = 0;
while (n > 0) {
n >>= 1;
length++;
}
return length;
};Explanation
Initialization:
- Start with
length = 0to count the number of bits.
- Start with
Right Shift Operation:
n >>= 1shifts the bits ofnto the right by 1 position, effectively dividingnby 2.- This removes the least significant bit (LSB) of
nin each iteration.
Counting Bits:
- Each right shift reduces the number of bits in
nby 1. - Increment
lengthby1for each shift untilnbecomes0.
- Each right shift reduces the number of bits in
Termination:
- The loop stops when
nis reduced to0, meaning all bits have been processed. - The final value of
lengthis the total number of bits required to represent the original numbern.
- The loop stops when
Example
For n = 10 (binary 1010):
- Iteration 1:
n = 10 >> 1 = 5(binary101),length = 1 - Iteration 2:
n = 5 >> 1 = 2(binary10),length = 2 - Iteration 3:
n = 2 >> 1 = 1(binary1),length = 3 - Iteration 4:
n = 1 >> 1 = 0(binary0),length = 4
The function returns 4, which is the correct number of bits for 10 (binary1010).
Why This Works
- Each right shift (
>>= 1) removes the LSB ofn, effectively counting one bit. - The loop continues until all bits are removed (
n = 0), ensuring all bits are counted. - This method works for any positive integer and is efficient for bit-length calculation.
5. Multiplying a Number by Using Left Shifts
The operation of multiplying a number x by
using a left shift (<<):
const multiplyByPowerOfTwo = (x: number, n: number): number => x << n;Explanation
Left Shift Operation:
- Shifting a number
xleft bynpositions is equivalent to multiplyingxby. - Each left shift doubles the value of
x.
- Shifting a number
Example:
- In binary:
5is0b101. Shifting left by3gives0b101000, which is40.
Efficiency:
- Bitwise shifts are extremely fast and avoid the overhead of multiplication.
Example
console.log(multiplyByPowerOfTwo(5, 3)); // 40 (5 * 2^3)
console.log(multiplyByPowerOfTwo(10, 2)); // 40 (10 * 2^2)6. Dividing a Number by Using Right Shifts
The operation of dividing a number x by
using a right shift (>>):
const divideByPowerOfTwo = (x: number, n: number): number => x >> n;Explanation
Right Shift Operation:
- Shifting a number
xright bynpositions is equivalent to dividingxbyand truncating the result (floor division). - Each right shift halves the value of
x.
- Shifting a number
Example:
- In binary:
40is0b101000. Shifting right by3gives0b101, which is5.
Efficiency:
- Bitwise shifts are faster than division and avoid floating-point operations.
Example
console.log(divideByPowerOfTwo(40, 3)); // 5 (40 / 2^3)
console.log(divideByPowerOfTwo(10, 1)); // 5 (10 / 2^1)Key Points
Left Shift (
<<):- Use to multiply a number by
. - Example:
x << nis equivalent to.
- Use to multiply a number by
Right Shift (
>>):- Use to divide a number by
(with truncation). - Example:
x >> nis equivalent to.
- Use to divide a number by
Efficiency:
- Bitwise shifts are faster than arithmetic operations like multiplication or division.
- They are ideal for performance-critical code.
Edge Cases
Negative Numbers:
- Right shifts on negative numbers preserve the sign bit (arithmetic right shift).
- Example:
-10 >> 1results in-5.
Zero:
- Shifting
0left or right always results in0.
- Shifting
Large Shifts:
- Shifting beyond the bit length of the number results in
0for positive numbers.
- Shifting beyond the bit length of the number results in
7. Finding the Unique Number in an Array Using XOR
The following function getUnique finds the unique number in an array of
numbers where every number except one appears twice. This is achieved using the
XOR operator:
const getUnique = (arr: number[]): number => arr.reduce((a, b) => a ^ b, 0);Explanation
XOR Properties:
- XOR (
^) is a bitwise operation with the following properties:a ^ a = 0(any number XORed with itself results in 0).a ^ 0 = a(any number XORed with 0 results in the number itself).- XOR is commutative and associative, meaning the order in which the operation is performed does not matter.
- XOR (
Behavior:
- When applying XOR across an array, pairs of identical numbers will cancel each other out (because of
a ^ a = 0). - The only number that does not have a pair will remain after all XOR operations, as it will be XORed with
0(which does not change the value).
- When applying XOR across an array, pairs of identical numbers will cancel each other out (because of
Initial Value:
- The
reduce()method initializes with0to start the XOR operation.
- The
Time Complexity:
O(n)wherenis the number of elements in the array. This is because we are performing the XOR operation on each element once.
Example
const arr1 = [4, 1, 2, 1, 2];
console.log(getUnique(arr1)); // 4 (4 is the unique number)
const arr2 = [7, 3, 5, 7, 3];
console.log(getUnique(arr2)); // 5 (5 is the unique number)Comparison with Other Approaches
Using a Hash Set:
- We could also solve this problem by using a hash set (or map) to track the counts of numbers, but this would require additional space, making it less efficient in terms of space complexity (
O(n)). - Space Complexity:
O(n) - Time Complexity:
O(n)
- We could also solve this problem by using a hash set (or map) to track the counts of numbers, but this would require additional space, making it less efficient in terms of space complexity (
XOR Approach:
- The XOR approach is space-efficient because it doesn't require additional data structures. It only uses a single variable to accumulate the result.
- Space Complexity:
O(1) - Time Complexity:
O(n)
Edge Cases
Array with only one element:
- The XOR approach will work correctly, as it will return the only element.
Array with all elements being the same:
- If all elements are the same (even number of elements), the result will be
0, indicating there is no unique number.
- If all elements are the same (even number of elements), the result will be
Empty array:
- If the array is empty, the result will be
0, since we initialize the accumulator to0.
- If the array is empty, the result will be
Example with Explanation:
Given an array where all elements except one appear twice:
const arr = [8, 3, 2, 3, 2];
console.log(unique(arr)); // Output: 8 (8 is the unique number)Step-by-Step XOR Process: 4. 0 ^ 8 = 8 5. 8 ^ 3 = 11 6. 11 ^ 2 = 9 7.9 ^ 3 = 8 8. 8 ^ 2 = 8
At the end of the process, the result is 8, which is the unique number in the array.
Key Points
- The XOR approach is very efficient for finding a unique number in an array where all other numbers appear in pairs.
- It operates in linear time complexity
O(n)and constant spaceO(1). - XOR's properties make it an ideal fit for pairing and cancellation problems.