Jc-alt logo
jc
data structures and algorithms

LeetCode: Bit Manipulation

LeetCode: Bit Manipulation
17 min read
#data structures and algorithms
Table Of Contents

Bit Manipulation Intro

What is Bit Manipulation

bits!

Bit Manipulation IRL

bits!

Bit Manipulation Application: Bit Manipulation

Pattern: bits!

Ex: bits numbers!!

    def bitss!(n: int) -> int:

        return n+1

136. Single Number ::1:: - Easy

Topics: Array, Bit Manipulation

Intro

Given a non-empty array of integers nums, every element appears twice except for one. Find that single one. You must implement a solution with a linear runtime complexity and use only constant extra space.

Example InputOutput
nums = [2,2,1]1
nums = [4,1,2,1,2]4
nums = [1]1

Constraints:

1 ≤ nums.length ≤ 3 * 104

-3 * 104 ≤ nums[i] ≤ 3 * 104

Each element in the array appears twice except for one element which appears only once.

Abstraction

Given a list of integers, find the integer that only appears once.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: [XOR] Duplicate Cancellation Single Identity Trick - Bit Manipulation/Bit Manipulation

    def singleNumber(self, nums: List[int]) -> int:
        
        # XOR Cancellation:
        # Every number appears twice except one, return the single number 

        # Idea:
        # XOR cancels duplicate numbers

        # XOR Properties:
        # 1. a ^ a = 0      (duplicates cancel)
        # 2. a ^ 0 = a      (identity)
        # 3. XOR is commutative and associative
    
        # Running Product 
        result = 0

        # tc: iterate over n O(n)
        for num in nums:

            # XOR over all numbers, duplicates are remove, single remains
            # tc: O(1)
            result ^= num

        # overall: tc O(n)
        # overall: sc O(1)
        return result

191. Number of 1 Bits ::2:: - Easy

Topics: Divide and Conquer, Bit Manipulation

Intro

Given a positive integer n, write a function that returns the number of set bits in its binary representation (also known as the Hamming weight). A set bit refers to a bit in the binary representation of a number that has a value of 1. Follow up: If this function is called many times, how would you optimize it?

Example InputOutput
n = 113
n = 1281
n = 214748364530

Constraints:

1 ≤ n ≤ 231 - 1

Abstraction

Given an integer, return how many bits are set to 1 in its binary representation.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: [(>>) Shift and (& 1) Mask ] Shift All Bits By 1 And Bit Mast Rightmost - Bit Manipulation/Bit Manipulation

    def hammingWeight(self, n: int) -> int:
        
        # Basic Shift + Mask:
        # Count number of set bits (1s) in binary representation

        # Idea:
        # - Check lowest bit using AND mask
        # - Shift bits right to process next position

        # Bit Properties:
        # 1. n & 1 = returns rightmost bit (1 if set, 0 if not)
        # 2. n >> 1 = shift all bits right by one position

        # Running Count:
        # stores total number of set bits found so far
        # sc: O(1)
        count = 0

        # tc: iterate over 32 bits (problem constraint) O(1)
        for _ in range(32):
            
            # Grab rightmost bit, add to running count
            # 1 if set, 0 is not
            # tc: O(1)
            count += n & 1 

            # Shift all bits right by one position, to process next bit
            # tc: O(1)
            n >>= 1

        # overall: tc O(32) ~= O(1)
        # overall: sc O(1)
        return count

Solution 2: [Brian Kernighan] Brian Kernighan Algorithm [TC Opt] - Bit Manipulation/Bit Manipulation

    def hammingWeight(self, n: int) -> int:
        
        # Brian Kernighan Algorithm
        # Count number of set bits efficiently

        # Idea:
        # Each operation removes ONE set bit

        # Trick:
        # n & (n - 1) clears the rightmost set bit

        # n     = 101000
        # n - 1 = 101111  &
        # -----------------
        # res   = 100000

        # Running Count:
        # counts how many set bits have been removed
        # sc: O(1)
        count = 0

        # Continues while any bit is set
        # Runs only k times (k = number of set bits)
        # tc: O(k)
        while n:
            
            # Remove the lowest (rightmost) set bit
            n &= (n - 1)

            # Increment count for removed bit
            count += 1

        # overall: tc O(k) =~ O(1)
        # overall: sc O(1)
        return count

338. Counting Bits ::2:: - Easy

Topics: Dynamic Programming, Bit Manipulation

Intro

Given an integer n, return an array ans of length n + 1 such that for each i (0 < = i < = n), ans[i] is the number of 1's in the binary representation of i.

Example InputOutput
n = 2[0,1,1]
n = 5[0,1,1,2,1,2]

Constraints:

0 ≤ n ≤ 105

Abstraction

Given an integer, return an array representing 0 -> n, where each index is the number of set bits for that integer.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: [5 (101) == 2 (10) + 5's Rightmost bit (1)] DP with Integer Division + Bit Shift - Bit Manipulation/Bit Manipulation

    def countBits(self, n: int) -> List[int]:
        
        # Dynamic Programming + Bit Manipulation
        # For every number i in [0, n], compute the number of set bits (1s)

        # Idea:
        # Reuse previously computed results using DP

        # Bit Properties:
        # 1. n & 1 = returns rightmost bit (1 if set, 0 if not)
        # 2. n >> 1 = shift all bits right by one position

        # - i >> 1 gives the number without its last bit
        # - (i & 1) tells whether that removed bit was a 1

        # (101) == (10) + 1 Example:
        # i = 5  => (101)
        # 5 >> 1 = 2 => (10)
        # dp[5] = dp[2] + 1

        # dp[i] = number of set bits in i
        
        # dp[0] = 0 by definition
        # sc: O(n)
        res = [0] * (n+1)


        # Build DP Bottom Up
        # tc: O(n)
        for i in range(1, n + 1):

            # Remove lowest bit
            prev_num_removing_rightmost = i >> 1

            # Check lowest bit
            current_num_rightmost_bit = i & 1

            # DP Recurrence:
            # dp[i] = dp[i >> 1] + (i & 1)
            # bits(i) = bits(i without last bit) + last bit (may be set or unset)
            res[i] = res[prev_num_removing_rightmost] + current_num_rightmost_bit

        # overall: tc O(n)
        # overall: sc O(n)
        return res

190. Reverse Bits ::1:: - Easy

Topics: Divide and Conquer, Bit Manipulation

Intro

Reverse bits of a given 32 bits signed integer. Follow up: If this function is called many times, how would you optimize it?

Example InputOutput
n = 43261596964176192
n = 21474836441073741822

Constraints:

0 ≤ n ≤ 231 - 2

n is even

Abstraction

Reverse bits of a 32 signed integer.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: Iterative Bit Manipulation - Bit Manipulation/Bit Manipulation

    def reverseBits(self, n: int) -> int:

        # Iterative Bit Manipulation
        # Reverse all 32 bits of an integer

        # Idea:
        # Build teh result one bit at a time

        # Process:
        # - Read n from right to left (Least Significant/Smallest to Most Significant/Largest)
        # - Build result from left to right

        # Running result stores reversed bits build so far
        # sc: O(1)
        reversed = 0

        # Iterate over 32 bits (problem constraint)
        # tc: O(1)
        for _ in range(32):

            # Shift result left
            # Creates space for next incoming bit
            # tc: O(1)
            reversed <<= 1

            # Grab lowest/rightmost bit from n (0 or 1)
            # tc: O(1)
            rightmost_digit = n & 1

            # Add
            # tc: O(1)
            reversed |= rightmost_digit
            
            # Shift n right:
            # Prepare next bit for extraction 
            # tc: O(1)
            n >>= 1
        
        # overall: tc O(32) =~ O(1)
        # overall: sc O(1)
        return reversed

268. Missing Number ::2:: - Easy

Topics: Array, Hash Table, Math, Binary Search, Bit Manipulation, Sorting

Intro

Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array. Follow up: Could you implement a solution using only O(1) extra space complexity and O(n) runtime complexity?

Example InputOutput
nums = [3,0,1]2
nums = [0,1]2
nums = [9,6,4,2,3,5,7,0,1]8

Constraints:

n == nums.length

1 ≤ n ≤ 104

0 ≤ nums[i] ≤ n

All the numbers of nums are unique.

Abstraction

Given an array of n distinct numbers, in range [0, n], return the only number that is missing from the range.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: Verify Against Math Formula - Bit Manipulation/Bit Manipulation

    def missingNumber(self, nums: List[int]) -> int:
        
        # Math Approach:
        # Find missing number from range [0, n]

        # Idea:
        # Compare Excepted vs Actual Sum
        #   - sum of [0, n]
        #   - actual sum of array

        # sum(0..n) = n * (n+1) // 2

        # missing number = expected_sum - actual sum

        n = len(nums)
        
        # Sum of numbers 0 to n
        expected_sum = n * (n + 1) // 2
        
        # Running sum of array elements
        actual_sum = 0

        # tc: O(n)
        for n in nums:
            actual_sum += n

        # Compute missing value
        missing = expected_sum - actual_sum

        # overall: tc O(n)
        # overall: sc O(1)
        return missing

Solution 2: [XOR] Cancel Duplicates Trick - Bit Manipulation/Bit Manipulation

    def missingNumber(self, nums: List[int]) -> int:
        
        # XOR Cancellation Trick
        # Find missing number from range [0, n]

        # Idea:
        # XOR cancels duplicates

        # XOR Properties:
        # 1. a ^ a = 0  (duplicates cancel)
        # 2. a ^ 0 = a  (identity)
        # 3. XOR is commutative + associative

        # If we XOR:
        #  all indices (0..n)
        #  with all array values

        # every paired number cancels, leaving only the missing number

        n = len(nums)

        # Running XOR Result
        # Holds XOR of processed indices and numbers
        # sc: O(1)
        missing = 0

        # tc: iterate over n O(n)
        for i in range(n + 1):

            # XOR current index
            missing ^= i

            # XOR Corresponding value (if exists)
            if i < n: 
                missing ^= nums[i]

        # overall: tc O(n)
        # overall: sc O(n)
        return missing

371. Sum of Two Integers ::1:: - Medium

Topics: Math, Bit Manipulation

Intro

Given two integers a and b, return the sum of the two integers without using the operators + and -.

Example InputOutput
a = 1, b = 23
a = 2, b = 35

Constraints:

Abstraction

Given two integers, return the sum without using operators.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: Bit Manipulation - Bit Manipulation/Bit Manipulation

    def getSum(self, a: int, b: int) -> int:
        
        # Bit Manipulation Addition
        # Add two integers without '+' or '-'

        # Idea:
        # Binary addition can be split into:
        #   1. XOR (^) => sum WITHOUT carry
        #   2. AND (&) => determine if bits exist
        #   3. AND (&) + left shift (<<) => carry bits 

        # Example Truth Table:
        # -------------------------------
        #    a    b     XOR(sum)    Carry
        #    0    0        0          0
        #    0    1        1          0
        #    1    0        1          0
        #    1    1        0          1

        # Variables:
        # sum_without_carry = a ^ b
        # carry             = (a & b) << 1

        # Repeat until carry becomes 0

        # Python Specific:
        # Python integers are unlimited in size, 
        # but this problem expects 32-bit signed integers range: -2^31 => 2^31 - 1

        # Example:
        #   a = 4294967295
        #   32-bit binary = 11111111 11111111 11111111 11111111
        #   python sees = +4294967295

        # Two's Complement Rule:
        # This differs from pythons view, here the leftmost bit acts as the sign bit
        # Leftmost bit = sign bit
        #   1 => negative number
        #   0 => positive number

        # Example:
        #   a = 4294967295
        #   32-bit binary = 11111111 11111111 11111111 11111111
        #   Two's Complement Sees = -1 

        # So we check:
        #   if a > maxInt:
        # as this number should be negative

        # Converting 32-bit Unsigned Integer To 32-bit Signed Integer
        # ~(a ^ MASK)


        # Mask to limit to 32 bits
        MASK = 0xFFFFFFFF

        # MaxInt to compare 32-bit Unsigned at the end
        maxInt = 2**31 - 1


        # tc: iterate while carry is not 0 O(?)
        while b != 0:

            # Sum without carry:
            # XOR keeps bits where exactly one is set
            xor_sum = a ^ b

            # restrict to 32 bits
            sum = xor_sum & MASK

            # Carry Calculation:
            # AND finds positions where BOTH bits are 1 (results in a carry)
            carry_bits = a & b

            # shift carry to next higher bit position
            shifted_carry = carry_bits << 1

            # restrict to 32 bits
            carry = shifted_carry & MASK
            
            # Update for the next iteration
            a = sum
            b = carry

        # If result exceeds max signed int,
        # we must converted a 32-bit unsigned value into a 32-bit signed
        if a <= maxInt:
            res = a
        else:
            # Conversion from 32-bit unsigned integer to 32-bit signed integer:

            # flip bits outside mask range
            flipped = a ^ MASK

            # apply bitwise NOT
            res = ~flipped

        # overall: tc O(1)
        # overall: sc O(1)
        return res

7. Reverse Integer ::1:: - Medium

Topics: Math

Intro

Given a signed 32-bit integer x, return x with its digits reversed. If reversing x causes the value to go outside the signed 32-bit integer range [-2^31, 2^31 - 1], then return 0. Assume the environment does not allow you to store 64-bit integers (signed or unsigned).

Example InputOutput
x = 123321
x = -123-321
x = 12021

Constraints:

-231 ≤ x ≤ 231 - 1

Abstraction

Given a 32-bit integer, reverse it.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: Iterative Math Approach - Bit Manipulation/Bit Manipulation

    def reverse(self, x: int) -> int:
        
        # Reverse Integer Iteratively:
        
        # Idea:
        #   Extract digits from right to left using (mod 10)
        #   Append to result by multiplying current result by 10 and adding the digit
        #   Track sign separately
        #   Ensure 32-bit signed integer overflow check

        # 32-bit signed integer limits
        maxInt = 2**31 - 1 # 2147483647
        minInt = -2**31    # -2147483648

        # Reversed Result
        # sc: O(1)
        res = 0

        # Tracking Sign
        sign = 1 if x >= 0 else -1

        # Work with absolute value
        x = abs(x)

        # Process digits iteratively
        while x != 0:

            # Grab rightmost digit
            digit = x % 10

            # Remove digit from x
            x //= 10

            # Overflow Check:
            # If res*1- + digit would exceed 32-bit signed integer, return 0
            # Example:
            # res = 214748364, digit = 8
            # res*10 + digit = 2147483648 > intMax => overflow
            # Check overflow before multiplying by 10
            if res > (maxInt - digit) // 10:
                return 0

            # Append digit to revered number
            # res_new = res*10 + digit
            res = res * 10 + digit

        # Apply original sign
        # Positive number => same
        # Negative number => multiply by -1
        res1 = sign * res

        # overall: tc O(log10(x))
        # overall: sc O(1)
        return res1