Search code examples
pythonlistalgorithmbit-manipulationbitwise-operators

Count the Subarray's which has bitwise and of its elements equal to Zero


Suppose we have a list of numbers

[7,2,9,8,6,12,109,28,14,19]

I want to find a efficient way to count the all sub-list of this list which will have bitwise and equal to zero

like:

[2,5] # 2&5 = 0
[9,5,7,2] # 9&5&7&2 = 0

Let me know if anyone know how to do it most efficiently


Solution

  • You can do this in O(n) time using a sliding window.

    If we know that a subarray of A on indices [L, R] has an & of zero, then we know that all larger subarrays ([L, R + 1], [L, R + 2], [L, R + 3], ...) will also have an & of zero due to monotonicity of & under inclusion.

    For example, computing the partial & products of an array from the left:

    Array (Base 10): [  7,  3,   10,   5]
    
    Array (Binary) : [111, 11, 1010, 101]
    
    Partial &s     : [111, 11,   10,   0]
    

    We'll iterate over the right end of our sliding window, while storing the smallest left end (i.e. the largest window) such that A[left, left + 1, ... right] has a nonzero bitwise &. Note that this left end can only ever increase as the right end increases.

    To update our window, we'll need to

    1. Be able to take an & of our window with A[right] (easy)
    2. Be able to remove the & of A[left] from our window (hard)

    To do removals efficiently, we'll instead track the count of set-bits for all elements in our window at each bit position. This lets us add and remove elements from our window, and test whether the & of our window is zero by testing whether each set bit position has a count less than the length of the window.

    Here's a walkthrough of the maximal nonzero windows on an example array:

    Base 10:
    A = [7, 2, 9, 8, 6, 12, 109, 28, 14, 19]
    
    Binary:
    111, 10, 1001, 1000, 110, 1100, 1101101, 11100, 1110, 10011
    
    
    Maximal nonzero [windows] at each right endpoint:
    
    
    --------------------------------------------------------------
    [111], 10, 1001, 1000, 110, 1100, 1101101, 11100, 1110, 10011
    ^   ^
    
    left = 0, right = 0, size = 1
    
    
    
    --------------------------------------------------------------
    [111, 10], 1001, 1000, 110, 1100, 1101101, 11100, 1110, 10011
    ^       ^
    
    left = 0, right = 1, size = 2
    
    
    
    --------------------------------------------------------------
    111, 10, [1001], 1000, 110, 1100, 1101101, 11100, 1110, 10011
             ^    ^                                         
    
    left = 2, right = 2, size = 1
    
    
    
    --------------------------------------------------------------
    111, 10, [1001, 1000], 110, 1100, 1101101, 11100, 1110, 10011
             ^          ^                                         
    
    left = 2, right = 3, size = 2
    
    
    
    --------------------------------------------------------------
    111, 10, 1001, 1000, [110], 1100, 1101101, 11100, 1110, 10011
                         ^   ^
    
    left = 4, right = 4, size = 1
    
    
    
    --------------------------------------------------------------
    111, 10, 1001, 1000, [110, 1100], 1101101, 11100, 1110, 10011
                         ^         ^
    
    left = 4, right = 5, size = 2
    
    
    
    --------------------------------------------------------------
    111, 10, 1001, 1000, [110, 1100, 1101101], 11100, 1110, 10011
                         ^                  ^
    
    left = 4, right = 6, size = 3
    
    
    
    --------------------------------------------------------------
    111, 10, 1001, 1000, [110, 1100, 1101101, 11100], 1110, 10011
                         ^                         ^
    
    left = 4, right = 7, size = 4
    
    
    
    --------------------------------------------------------------
    111, 10, 1001, 1000, [110, 1100, 1101101, 11100, 1110], 10011
                         ^                               ^   
    
    left = 4, right = 8, size = 5
    
    
    
    --------------------------------------------------------------
    111, 10, 1001, 1000, 110, 1100, 1101101, 11100, [1110, 10011]
                                                    ^           ^   
    
    left = 8, right = 9, size = 2
    

    Here, the sum of nonzero-& window sizes is 23. As a length 10 array has 55 possible subarrays, the answer is 55 - 23 = 32 bitwise-&-zero subarrays.

    Python code:

    def count_bitwise_and_zero(nums: List[int]) -> int:
        """Count nonempty subarrays with & of elements equal to 0.
        Given a list on nonnegative integers,
        returns the number of contiguous, nonempty subarrays for which the
        bitwise and of all elements in the subarray are equal to 0.
    
        Runs in linear time on fixed-width integers.
        """
    
        def get_set_bit_indices(x: int) -> List[int]:
            """Return indices of set bits in x"""
            pow_2 = 1
            exponent = 0
            set_bits = []
    
            while pow_2 <= x:
                if pow_2 & x != 0:
                    set_bits.append(exponent)
    
                exponent += 1
                pow_2 *= 2
    
            return set_bits
    
        def is_bitwise_and_zero(window_length: int, bit_counts: Dict[int, int]) -> bool:
            """Given bit counts for a window of an array, return whether the
            window's elements have bitwise AND equal to zero."""
            return all(value < window_length for value in bit_counts.values())
    
        n = len(nums)
        total_subarray_count = n * (n + 1) // 2
    
        nonzero_subarray_count = 0
        window_bit_counts = Counter()
    
        """At every iteration start, [left_idx, right_idx] is the longest subarray
        ending at right_idx whose bitwise AND is nonzero."""
        left_idx = 0
    
        for right_idx, right_element in enumerate(nums):
            if right_element == 0:
                window_bit_counts.clear()
                left_idx = right_idx + 1
                continue
    
            # Expand window to include right
            window_bit_counts += Counter(get_set_bit_indices(right_element))
    
            # Shrink window until AND is nonzero
            while (left_idx < right_idx
                   and is_bitwise_and_zero(right_idx - left_idx + 1, window_bit_counts)):
    
                window_bit_counts -= Counter(get_set_bit_indices(nums[left_idx]))
                left_idx += 1
    
            nonzero_subarray_count += (right_idx - left_idx + 1)
    
        return total_subarray_count - nonzero_subarray_count
    

    Example usage:

    count_bitwise_and_zero([7, 2, 9, 8, 6, 12, 109, 28, 14, 19])
    >>> 32
    

    N.B. There's an assumption that the maximum bit-width of the integers is constant. To get a linear-time solution when integers can be arbitrarily large, you'll need to keep a meta-counter, which tracks counts of counts of set bits.

    As an exercise, you can try to generalize to the problem of bitwise & equal to some target t which may be greater than zero; it takes a bit more work.