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:
-
Hash Table + Enumeration: First, fix the right endpoint
r
and iterate over possible left endpointsl
. For each potential subarray ending atr
, calculate the bitwiseAND
of the subarray and maintain a count of different outcomes using a hash table (Counter
in Python). -
Dynamic Maintenance: As the right endpoint extends (i.e.,
r
increases), update possible results by computing the bitwiseAND
of each previous observed result withnums[r]
. This accounts for new subarrays that end at the newr
. -
Count Occurrences: Each time a subarray with a bitwise
AND
result matchingk
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:
-
Initialization:
- Use a
Counter
, namedpre
, to store the frequency of each possible result from the bitwiseAND
operation as subarrays are considered. TheCounter
helps in maintaining counts of specific numbers rapidly. - Initialize an
ans
variable to zero, which will hold the final count of valid subarrays.
- Use a
-
Iterate through Array Elements:
- For each element
x
innums
, initialize a newCounter
,cur
, to store the results of calculations involvingx
.
- For each element
-
Calculate Bitwise AND Results:
- For each previous result,
y
, in thepre
Counter, computex & y
and update thecur
Counter with this new result and its associated count. This step effectively extends all subarrays that previously gave the resulty
to include the new elementx
. - Additionally, add
x
itself tocur
with a count of 1 sincex
can be the start of new subarrays.
- For each previous result,
-
Update the Answer:
- Add to
ans
the count of timesk
appears in thecur
Counter. This step ensures that all subarrays ending at the current elementx
and having a bitwiseAND
equal tok
are counted.
- Add to
-
Prepare for Next Iteration:
- Assign
cur
topre
for the next iteration. This transfer means that all potential subarrays ending right before moving to the next element are accessed efficiently.
- Assign
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 EvaluatorExample 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
.
-
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.
- Start with
-
Processing 2:
x = 2
, initializecur = Counter()
.- Calculate
x & y
for each result inpre
. Sincepre
is empty, this step is skipped. - Add
2 & 2
tocur
, i.e.,cur[2] = 1
. - No subarray matches
k
yet, soans
remains0
. - Set
pre = cur
, so nowpre = Counter({2: 1})
.
-
Processing 1:
x = 1
, initializecur = Counter()
.- Calculate
x & y
for each result inpre
:1 & 2 = 0
, socur[0] = 1
. - Add
1 & 1
tocur
, i.e.,cur[1] = 1
. cur
has1
(matchingk=1
) once, so incrementans
by1
. Now,ans = 1
.- Set
pre = cur
, sopre = Counter({0: 1, 1: 1})
.
-
Processing 3:
x = 3
, initializecur = Counter()
.- Calculate
x & y
for each result inpre
:3 & 0 = 0
, updatecur[0] = 1
.3 & 1 = 1
, updatecur[1] = 1
.
- Add
3 & 3 = 3
tocur
, i.e.,cur[3] = 1
. cur
has1
again (matchingk=1
), so incrementans
by1
. 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, wheren
is the length of thenums
array. - For each element
x
innums
, the inner loop iterates over each entryy, v
in thepre
dictionary, wherepre
is aCounter
of previously computed bitwise AND values. - The size of
pre
can be proportional tolog M
due to the nature of bitwise operations, whereM
is the maximum value innums
. - 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
andcur
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.
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
Segment Tree Faster Range Queries For this article we want to introduce the idea of a Segment Tree Segment Trees allow us to quickly perform range queries as well as range updates Suppose we have an array and we want to know the sum of a particular range of numbers as well
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!