Lecture 1 (Bit Manupulation)¶
Binary Number Conversion¶
Decimal to binary conversion¶
By repeatedly dividing a number by 2 and recording the result, decimal values can be transformed into binary.
Converting 13 to its binary equivalent:
Start with the decimal number 13.
Divide the number by 2 and record the remainder.
Repeat the division with the quotient until the number becomes 0.
13 / 2 = 6 remainder 1
6 / 2 = 3 remainder 0
3 / 2 = 1 remainder 1
1 / 2 = 0 remainder 1
To obtain the binary equivalent of 13, read the remainders from bottom to top: 1101.
Approach 1¶
- loop with string concat
def decimal_to_binary(num):
if num ==0:
return 0
result = ""
while num >0 :
result += str(num % 2)
num = num // 2
return result[::-1]
print(decimal_to_binary(13))
1101
Complexity¶
M : number of digits in binary represenation of num = log2(num)
string concat is expensive (O(M))
main loop (O(M))
O(M^2)
O(M)
Approach 2 (right shift operator)¶
- to optimse space and avoid string concat
def decimal_to_binary(num):
if num ==0:
return "0"
result = []
while num >0 :
val = str(num %2)
result.append(val)
num = num // 2
return "".join(result[::-1])
print(decimal_to_binary(13))
1101
Approach 3 (Built in )¶
def decimal_to_binary(num):
return bin(num)[2:]
print(decimal_to_binary(13))
1101
Binary to decimal conversion¶
Converting a binary number back to its decimal equivalent involves a reverse process.
Example: Converting 1101 to its decimal equivalent:
Start from the rightmost bit (least significant bit). Each bit is multiplied by 2 raised to the power of its position index. 1 * 2^0 = 1 0 * 2^1 = 0 1 * 2^2 = 4 1 * 2^3 = 8 Sum = 1 + 0 + 4 + 8 = 13.
Approach 1 (single look)¶
def binary_to_decimal(s):
n = len(s)
num =0
for i in range(n-1,-1,-1):
val = int(s[i])
pos = n-i-1
num += val * (2 ** pos)
return num
print(binary_to_decimal("1101"))
13
Complexity¶
- 2 ** pos : is not O(1) it takes O(log pos)
- N : len(s)
- O(N log N)
- O(1)
Approach 2 (Maintain running pow)¶
- instead of power operation , just maintain the running power
def binary_to_decimal(s):
n = len(s)
num =0
pow = 1
for i in range(n-1,-1,-1):
val = int(s[i])
num += val * (pow)
pow = pow * 2
return num
print(binary_to_decimal("1101"))
13
Complexity¶
- N : len(s)
O(N)
O(1)
Approach 3 (Built in)¶
def binary_to_decimal(s):
return int(s,2)
print(binary_to_decimal("1101"))
13
Understanding one's complement and two's complement¶
One's complement¶
The one's complement of a binary number is obtained by flipping all the bits.
Binary of 13 : 0000 1101
One's Complement : 1111 0010
Two's complement¶
The two's complement is obtained by taking the one's complement of a number and adding 1.
Example: The two's complement of 13 (binary 1101):
One's Complement : 1111 0010
Add 1 : 1111 0011
num = 13
print(~num)
-14 -0b1110
Bitwise operators¶
And Operator¶
If both corresponding bits are 1, the resulting bit is 1; otherwise, it is 0.
13: 1101
7: 0111
& : 0101 → 5
num1 = 13
num2 = 7
result = num1 & num2
print(result)
5
Or Operator¶
If either corresponding bit is 1, the resulting bit is 1.
13: 1101
7: 0111
| : 1111 → 15
num1 = 13
num2 = 7
result = num1 | num2
print(result)
15
Xor Operator¶
If bits differ, the result is 1; if the same, result is 0.
13: 1101
7: 0111
^ : 1010 → 10
- a ^ a => 0
- a ^ 0 => a
- xor is commutative and associative
num1 = 13
num2 = 7
result = num1 ^ num2
print(result)
10
NOT Operator¶
Flips all bits of the number.
5: 0000 0101
~5: 1111 1010 → -6 (in two's complement)
num1 = 5
result = ~5
print(result)
-6
Shift Operators¶
num1 = 13
result = num1 >> 1
num = 13
result = num << 1
print(result)
26
Bit manupulation tricks and techniques¶
Swapping two numbers without third variable¶
num1 = 10
num2 = 20
num1 = num1 ^ num2
num2 = num2 ^ num1
num1 = num1 ^ num2
print(num1,num2)
20 10
Checking if the ith bit is set¶
(1 << i) & num → set if result ≠ 0
setting the ith bit¶
(1 << i) | num
Clearing the ith bit¶
~(1 << i) & num
Toggling the ith bit¶
(1 << i) ^ num
Check if the i-th bit is set or not¶
Given two integers n and i, return true if the ith bit in the binary representation of n (counting from the least significant bit, 0-indexed) is set (i.e., equal to 1). Otherwise, return false.
Input: n = 5, i = 0
Output: true
Explanation: Binary representation of 5 is 101. The 0-th bit from LSB is set (1).
Input: n = 10, i = 1
Output: true
Explanation: Binary representation of 10 is 1010. The 1-st bit from LSB is set (1).
Approach 1 (Little Complex expression)¶
-result = (1 << i ) * num == (1 <<i)
def check_i_set(num,i):
val = (1<< i) & num
return val == (1 <<i)
print(check_i_set(5,0))
print(check_i_set(10,1))
print(check_i_set(10,2))
True True False
Complexity¶
O(1)
O(1)
Approach 2 (Simple Expression)¶
- result = num & (1 << i) !=0
def check_i_set(num,i):
val = (1<< i) & num
return val != 0
print(check_i_set(5,0))
print(check_i_set(10,1))
print(check_i_set(10,2))
True True False
Check if number os odd or not¶
Given a non-negative integer n, determine whether it is odd. Return true if the number is odd, otherwise return false. A number is odd if it is not divisible by 2 (i.e., n % 2 != 0).
Input: n = 7
Output: true
Input: n = 10
Output: false
Approach 1 (remainder)¶
def is_odd(num):
return num%2 ==1
print(is_odd(7))
print(is_odd(10))
True False
Approach 2 (LSB is 1 or not)¶
def is_odd(num):
return num & 1 == 1
print(is_odd(7))
print(is_odd(10))
True False
Complexity¶
O(1)
O(1)
Check if a number is power of 2 or not¶
Given an integer n, return true if it is a power of two. Otherwise, return false. An integer n is a power of two if there exists an integer x such that n == 2ˣ.
Input: n = 16
Output: true
Input: n = 3
Output: false
Approach 1¶
- in binary representation , there will only be single 1 and rest 0
- that 1 must be left most
def is_pow_2(num):
if num <1:
return False
bin_str = str(bin(num)[2:])
n = len(bin_str)
if bin_str[0] =="0":
return False
for i in range(1,n):
if bin_str[i] == "1":
return False
return True
print(is_pow_2(16))
print(is_pow_2(3))
True False
Complexity¶
- M : log 2 (num)
- O(M)
- O(M)
Approach 2(Recommended)¶
- Powers of 2 have exactly one bit set: 8 = 1000₂
- Subtracting 1 flips all bits after that: 7 = 0111₂
- AND operation gives zero: 1000₂ & 0111₂ = 0000₂
- For non-powers of 2, this won't be zero
def is_pow_2(num):
if num < 2:
return False
return num & (num-1) ==0
print(is_pow_2(16))
print(is_pow_2(3))
True False
Complexity¶
O(1)
O(1)
Count the number of set bits¶
Given an integer n, return the number of set bits (1s) in its binary representation. Can you solve it in O(log n) time complexity?
Input: n = 5
Output: 2
Explanation: The binary representation of 5 is 101, which has 2 set bits.
Input: n = 15
Output: 4
Explanation: The binary representation of 15 is 1111, which has 4 set bits.
Approach 1 (Not clean)¶
- Check LSB and right shift
def count_set_bits(num):
count =0
pos =0
while num >0 :
val = (1 << pos) & num
if val !=0 :
count +=1
num = num >> 1
return count
print(count_set_bits(5))
print(count_set_bits(15))
2 4
Complexity¶
- M : no of bin digits in num = log 2 (nums)
O(M)
O(1)
Approach2 (Clean)¶
def count_set_bits(num):
count =0
while num >0 :
if num & 1:
count +=1
num = num >> 1
return count
print(count_set_bits(5))
print(count_set_bits(15))
2 4
Approach3 (Most efficient)¶
- clears the right most set bit not every position
def count_set_bits(num):
count =0
while num >0 :
num = num & (num -1)
count +=1
return count
print(count_set_bits(5))
print(count_set_bits(15))
2 4
Set the rightmost bit¶
Given a positive integer n, set the rightmost unset (0) bit of its binary representation to 1 and return the resulting integer. If all bits are already set, return the number as it is.
Input: n = 10 (binary: 1010)
Output: 11 (binary: 1011)
Explanation: The rightmost unset bit is the least significant bit (LSB). Setting it to 1 gives 1011 = 11.
Input: n = 7 (binary: 111)
Output: 7 (binary: 111)
Explanation: All bits are already set to 1, so the number remains the same.
Approach 1¶
- result = num | (num +1)
- n+1 flips right most 0 to 1 and rest to 0
- num | num +1 will not modify rest of the bits of num
def set_right_unset_bit(num):
# when all digits are already set
if (num & (num +1 )) ==0:
return num
return num | (num +1)
print(set_right_unset_bit(10))
print(set_right_unset_bit(7))
11 7
Get the right most set bit¶
num = 13 # 1101
unset_right_most_set_bit = (num & (num -1)) ## 1100 (unset the right most set bit)
print(unset_right_most_set_bit ^ num) # 1
num = 26 # 11010
unset_right_most_set_bit = (num & (num -1)) ## 11000
print(unset_right_most_set_bit ^ num) ## 2
1 2