Facebook Pixel

3209. Number of Subarrays With AND Value of K


Problem Description

Given an array of integers nums and an integer k, you are tasked with finding the number of subarrays of nums where the bitwise AND of the elements of the subarray equals k. A subarray is defined as a contiguous section of the array.

Intuition

The solution revolves around dynamically calculating possible results of bitwise AND operations for subarrays. This is achieved by fixing a right endpoint r and iterating through possible left endpoints l in this manner:

  1. Hash Table + Enumeration: First, fix the right endpoint r and iterate over possible left endpoints l. For each potential subarray ending at r, calculate the bitwise AND of the subarray and maintain a count of different outcomes using a hash table (Counter in Python).

  2. Dynamic Maintenance: As the right endpoint extends (i.e., r increases), update possible results by computing the bitwise AND of each previous observed result with nums[r]. This accounts for new subarrays that end at the new r.

  3. Count Occurrences: Each time a subarray with a bitwise AND result matching k is found, increase the count. Different subarray combinations are considered as new elements are added to the tracking collection.

By dynamically maintaining possible results and their counts and extending each subarray step-by-step, the solution efficiently counts valid subarrays in linear time complexity relative to the array length, taking advantage of the property that bitwise AND results in decreasing values as more elements are included in the subarray.

Learn more about Segment Tree and Binary Search patterns.

Solution Approach

The approach leverages a combination of hash tables and iterative enumeration to efficiently count subarrays with a bitwise AND result equal to k. Here's how it works:

  1. Initialization:

    • Use a Counter, named pre, to store the frequency of each possible result from the bitwise AND operation as subarrays are considered. The Counter helps in maintaining counts of specific numbers rapidly.
    • Initialize an ans variable to zero, which will hold the final count of valid subarrays.
  2. Iterate through Array Elements:

    • For each element x in nums, initialize a new Counter, cur, to store the results of calculations involving x.
  3. Calculate Bitwise AND Results:

    • For each previous result, y, in the pre Counter, compute x & y and update the cur Counter with this new result and its associated count. This step effectively extends all subarrays that previously gave the result y to include the new element x.
    • Additionally, add x itself to cur with a count of 1 since x can be the start of new subarrays.
  4. Update the Answer:

    • Add to ans the count of times k appears in the cur Counter. This step ensures that all subarrays ending at the current element x and having a bitwise AND equal to k are counted.
  5. Prepare for Next Iteration:

    • Assign cur to pre for the next iteration. This transfer means that all potential subarrays ending right before moving to the next element are accessed efficiently.

Using this method ensures that as each new element is processed, all possible preceding results are taken into account. This makes the implementation time-efficient for the problem's constraints:

class Solution:
    def countSubarrays(self, nums: List[int], k: int) -> int:
        ans = 0
        pre = Counter()  # This willcount the occurrences of specific AND results
        for x in nums:
            cur = Counter()  # For storing AND results that finish with the current element
            for y, v in pre.items():
                cur[x & y] += v  # Extend previous results with the current element
            cur[x] += 1  # Consider subarray consisting of the current element itself
            ans += cur[k]  # Add to answer count of necessary results found
            pre = cur  # Move on to the next iteration with updated results
        return ans

This strategic use of counting through hash tables and bitwise operations provides a comprehensive and efficient solution to the problem.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a simple example to understand the solution approach.

Consider the array nums = [2, 1, 3] and k = 1. We need to find subarrays whose bitwise AND equals 1.

  1. Initialization:

    • Start with ans = 0 indicating the count of valid subarrays.
    • A pre Counter is initialized to keep track of previous results. Initially, it's empty.
  2. Processing 2:

    • x = 2, initialize cur = Counter().
    • Calculate x & y for each result in pre. Since pre is empty, this step is skipped.
    • Add 2 & 2 to cur, i.e., cur[2] = 1.
    • No subarray matches k yet, so ans remains 0.
    • Set pre = cur, so now pre = Counter({2: 1}).
  3. Processing 1:

    • x = 1, initialize cur = Counter().
    • Calculate x & y for each result in pre: 1 & 2 = 0, so cur[0] = 1.
    • Add 1 & 1 to cur, i.e., cur[1] = 1.
    • cur has 1 (matching k=1) once, so increment ans by 1. Now, ans = 1.
    • Set pre = cur, so pre = Counter({0: 1, 1: 1}).
  4. Processing 3:

    • x = 3, initialize cur = Counter().
    • Calculate x & y for each result in pre:
      • 3 & 0 = 0, update cur[0] = 1.
      • 3 & 1 = 1, update cur[1] = 1.
    • Add 3 & 3 = 3 to cur, i.e., cur[3] = 1.
    • cur has 1 again (matching k=1), so increment ans by 1. Now, ans = 2.
    • Prepare for next element (though the array ends here), by setting pre = cur.

At the end of the iteration, ans = 2, indicating there are 2 subarrays ([1] and [1, 3]) where the bitwise AND equals k=1.

Solution Implementation

1from typing import List
2from collections import Counter
3
4class Solution:
5    def countSubarrays(self, nums: List[int], k: int) -> int:
6        # Initialize the answer counter
7        ans = 0
8        # Initialize a counter to keep track of subarray bitwise AND results
9        previous_counts = Counter()
10
11        # Iterate over each number in the input list
12        for number in nums:
13            # Initialize a new counter for the current subarray bitwise AND results
14            current_counts = Counter()
15
16            # Update the current counter with results of AND operations
17            # between the current number and each previous result
18            for result, count in previous_counts.items():
19                current_counts[number & result] += count
20
21            # Each number forms a subarray with itself, so increment its count
22            current_counts[number] += 1
23
24            # If a subarray with a bitwise AND equal to k is found, add its count to the answer
25            ans += current_counts[k]
26
27            # Update previous_counts with the current counter for the next iteration
28            previous_counts = current_counts
29
30        return ans
31
1import java.util.HashMap;
2import java.util.Map;
3
4class Solution {
5    public long countSubarrays(int[] nums, int k) {
6        long ans = 0; // Initialize the answer to store the number of subarrays
7        Map<Integer, Integer> previousMap = new HashMap<>(); // Map to track previous results
8
9        // Iterate over each number in the input array
10        for (int currentNum : nums) {
11            Map<Integer, Integer> currentMap = new HashMap<>(); // Map to track current results
12
13            // Update currentMap based on the previous results
14            for (var entry : previousMap.entrySet()) {
15                int previousBitwiseAndResult = entry.getKey();
16                int countOfSubarrays = entry.getValue();
17                int currentBitwiseAndResult = currentNum & previousBitwiseAndResult;
18
19                currentMap.merge(currentBitwiseAndResult, countOfSubarrays, Integer::sum);
20            }
21
22            // Add the current number itself as a subarray to currentMap
23            currentMap.merge(currentNum, 1, Integer::sum);
24
25            // If there is a subarray with bitwise AND equal to k, add its count to the answer
26            ans += currentMap.getOrDefault(k, 0);
27
28            // Update previousMap to currentMap for the next iteration
29            previousMap = currentMap;
30        }
31
32        return ans;
33    }
34}
35
1class Solution {
2public:
3    long long countSubarrays(std::vector<int>& nums, int k) {
4        long long result = 0;  // Initialize the result which counts the subarrays
5        std::unordered_map<int, int> previousCounts;  // Map to store counts of bitwise AND results
6
7        // Iterate through each number in the vector
8        for (int num : nums) {
9            std::unordered_map<int, int> currentCounts;  // Temporary map for current iteration
10
11            // Update currentCounts with new AND results with previous results
12            for (auto& [andResult, count] : previousCounts) {
13                currentCounts[num & andResult] += count;
14            }
15
16            // Each number contributes to itself as a subarray
17            currentCounts[num]++;
18
19            // Add count of subarrays with AND result equal to k
20            result += currentCounts[k];
21
22            // Update previousCounts with current counts for the next iteration
23            previousCounts = currentCounts;
24        }
25
26        // Return the total count of subarrays whose AND result is k
27        return result;
28    }
29};
30
1function countSubarrays(nums: number[], k: number): number {
2    let ans = 0; // This variable holds the count of subarrays with bitwise AND equal to k.
3    let pre = new Map<number, number>(); // This map keeps track of previous computations of bitwise AND.
4
5    for (const x of nums) { // Iterate over each number in the array.
6        const cur = new Map<number, number>(); // Current map to store new computations of bitwise AND.
7      
8        for (const [y, v] of pre) { // Iterate over each entry in the previous map.
9            const z = x & y; // Calculate the bitwise AND of current number and previous keys.
10            cur.set(z, (cur.get(z) || 0) + v); // Update the count for the AND result in the current map.
11        }
12
13        // Each number is a subarray itself, update current map for the number.
14        cur.set(x, (cur.get(x) || 0) + 1); 
15      
16        // If the current AND result equals k, add to answer the count for k in current map.
17        ans += cur.get(k) || 0;
18
19        pre = cur; // Set previous map to current map for next iteration.
20    }
21
22    return ans; // Return the total count of subarrays with bitwise AND equals k.
23}
24

Time and Space Complexity

The time complexity of the code is O(n * log M). Here's why:

  • The outer loop runs n times, where n is the length of the nums array.
  • For each element x in nums, the inner loop iterates over each entry y, v in the pre dictionary, where pre is a Counter of previously computed bitwise AND values.
  • The size of pre can be proportional to log M due to the nature of bitwise operations, where M is the maximum value in nums.
  • The overall computation in the inner loop is limited to log M operations for each element due to the constraints of bitwise values.

The space complexity of the code is O(log M). Here's why:

  • The pre and cur dictionaries store combinations of bitwise AND results.
  • The space used by these dictionaries grows with the distinct bitwise AND results, which is limited by log M due to bitwise operation constraints.

Learn more about how to find time and space complexity quickly.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More