1248. Count Number of Nice Subarrays
Problem Description
You are given an array of integers nums
and an integer k
. Your task is to find how many continuous subarrays contain exactly k
odd numbers. These subarrays are called "nice" subarrays.
A subarray is a contiguous part of the array, and you need to count all possible subarrays where the total count of odd numbers equals k
.
For example, if nums = [1, 1, 2, 1, 1]
and k = 3
, you would look for all subarrays that have exactly 3 odd numbers. The subarrays [1, 1, 2, 1]
and [1, 2, 1, 1]
both contain exactly 3 odd numbers (the even number 2 doesn't affect the odd count).
The solution uses a prefix sum technique combined with a hash table. As we traverse the array, we keep track of the cumulative count of odd numbers seen so far (t
). The key insight is that if we have seen t
odd numbers up to the current position, and we want subarrays with exactly k
odd numbers, we need to find how many prefix arrays had t - k
odd numbers. The difference between these two positions would give us a subarray with exactly k
odd numbers.
The code uses v & 1
to check if a number is odd (this bitwise operation returns 1 for odd numbers and 0 for even numbers). The Counter
is initialized with {0: 1}
to handle the case where a subarray starts from the beginning of the array. For each element, we update the running count of odd numbers, check how many valid subarrays end at the current position by looking up cnt[t - k]
, and then update our counter for future positions.
Intuition
The key observation is that we need to count subarrays with exactly k
odd numbers. Instead of checking every possible subarray (which would be inefficient), we can transform this into a different problem: tracking the cumulative count of odd numbers as we traverse the array.
Think of it this way: if we know how many odd numbers we've seen up to position i
and how many we've seen up to position j
(where j < i
), then the subarray from j+1
to i
contains the difference between these two counts. So if we've seen t
odd numbers up to position i
, we want to find all positions j
where we had seen t - k
odd numbers. Each such position gives us a valid subarray.
This transforms our problem from "finding subarrays with exactly k
odd numbers" to "finding pairs of positions whose odd-count difference is k
". This is a classic pattern where we can use a hash table to store the frequency of each odd-count we've encountered.
Why does this work? Consider walking through the array and maintaining a running count of odd numbers. At each position, if our current count is t
, then any previous position with count t - k
can be the start of a valid subarray ending at our current position. By storing how many times we've seen each count value, we can instantly know how many valid subarrays end at the current position.
The initialization {0: 1}
handles the edge case where a valid subarray starts from the very beginning of the array. Without this, we'd miss counting subarrays that start at index 0 and have exactly k
odd numbers.
Learn more about Math, Prefix Sum and Sliding Window patterns.
Solution Approach
The solution implements a prefix sum technique combined with a hash table to efficiently count subarrays with exactly k
odd numbers.
Data Structures Used:
- A
Counter
(hash table)cnt
to store the frequency of each prefix sum value - Variables
ans
to store the final count andt
to track the running count of odd numbers
Algorithm Steps:
-
Initialize the counter: Start with
cnt = Counter({0: 1})
. The key0
with value1
means we've seen zero odd numbers once (before processing any elements). This handles subarrays starting from index 0. -
Traverse the array: For each number
v
innums
:-
Update odd count: Add
v & 1
tot
. The bitwise AND operationv & 1
returns1
ifv
is odd (last bit is 1) and0
ifv
is even (last bit is 0). -
Count valid subarrays: Look up
cnt[t - k]
and add it toans
. This represents how many times we've previously seent - k
odd numbers. Each such occurrence means there's a subarray ending at the current position with exactlyk
odd numbers. -
Update the counter: Increment
cnt[t]
by 1 to record that we've now seent
odd numbers up to this position.
-
-
Return the result: After processing all elements,
ans
contains the total count of nice subarrays.
Why This Works:
If we have t
odd numbers up to the current position and we previously had t - k
odd numbers at some earlier position(s), then the subarray between those positions contains exactly t - (t - k) = k
odd numbers. The hash table allows us to look up how many such earlier positions exist in O(1) time, making the overall algorithm O(n) in time complexity with O(n) space complexity for the hash table.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with nums = [1, 1, 2, 1, 1]
and k = 3
.
Initial State:
cnt = {0: 1}
(we've seen 0 odd numbers once, before starting)ans = 0
(no subarrays counted yet)t = 0
(running count of odd numbers)
Step 1: Process nums[0] = 1
1 & 1 = 1
(1 is odd), sot = 0 + 1 = 1
- Check
cnt[t - k] = cnt[1 - 3] = cnt[-2] = 0
(no previous position with -2 odd numbers) ans = 0 + 0 = 0
- Update
cnt[1] = 1
- State:
cnt = {0: 1, 1: 1}
,ans = 0
,t = 1
Step 2: Process nums[1] = 1
1 & 1 = 1
(1 is odd), sot = 1 + 1 = 2
- Check
cnt[2 - 3] = cnt[-1] = 0
ans = 0 + 0 = 0
- Update
cnt[2] = 1
- State:
cnt = {0: 1, 1: 1, 2: 1}
,ans = 0
,t = 2
Step 3: Process nums[2] = 2
2 & 1 = 0
(2 is even), sot = 2 + 0 = 2
- Check
cnt[2 - 3] = cnt[-1] = 0
ans = 0 + 0 = 0
- Update
cnt[2] = 2
(we've now seen 2 odd numbers at two positions) - State:
cnt = {0: 1, 1: 1, 2: 2}
,ans = 0
,t = 2
Step 4: Process nums[3] = 1
1 & 1 = 1
(1 is odd), sot = 2 + 1 = 3
- Check
cnt[3 - 3] = cnt[0] = 1
✓ (Found 1 valid subarray!)- This means subarray from start to current position [1,1,2,1] has exactly 3 odd numbers
ans = 0 + 1 = 1
- Update
cnt[3] = 1
- State:
cnt = {0: 1, 1: 1, 2: 2, 3: 1}
,ans = 1
,t = 3
Step 5: Process nums[4] = 1
1 & 1 = 1
(1 is odd), sot = 3 + 1 = 4
- Check
cnt[4 - 3] = cnt[1] = 1
✓ (Found 1 valid subarray!)- This means subarray from after index 0 to current position [1,2,1,1] has exactly 3 odd numbers
ans = 1 + 1 = 2
- Update
cnt[4] = 1
- State:
cnt = {0: 1, 1: 1, 2: 2, 3: 1, 4: 1}
,ans = 2
,t = 4
Final Result: 2
The algorithm correctly identifies two subarrays with exactly 3 odd numbers:
[1, 1, 2, 1]
- found when processing index 3[1, 2, 1, 1]
- found when processing index 4
Solution Implementation
1class Solution:
2 def numberOfSubarrays(self, nums: List[int], k: int) -> int:
3 # Dictionary to store frequency of prefix sums (count of odd numbers)
4 # Initialize with 0: 1 to handle subarrays starting from index 0
5 prefix_count = Counter({0: 1})
6
7 # Total number of valid subarrays
8 result = 0
9
10 # Running count of odd numbers encountered so far
11 odd_count = 0
12
13 # Iterate through each number in the array
14 for num in nums:
15 # Increment odd_count if current number is odd
16 # (num & 1) returns 1 if odd, 0 if even
17 odd_count += num & 1
18
19 # Check if there exists a prefix with (odd_count - k) odd numbers
20 # If yes, all subarrays between that prefix and current position
21 # will have exactly k odd numbers
22 result += prefix_count[odd_count - k]
23
24 # Update the frequency of current odd_count in the dictionary
25 prefix_count[odd_count] += 1
26
27 return result
28
1class Solution {
2 public int numberOfSubarrays(int[] nums, int k) {
3 int arrayLength = nums.length;
4
5 // Array to store frequency of odd number counts
6 // cnt[i] represents how many times we've seen exactly i odd numbers
7 int[] oddCountFrequency = new int[arrayLength + 1];
8
9 // Initialize: before processing any elements, we have 0 odd numbers once
10 oddCountFrequency[0] = 1;
11
12 int result = 0;
13 int currentOddCount = 0;
14
15 // Iterate through each number in the array
16 for (int number : nums) {
17 // Check if current number is odd and increment count if true
18 // (number & 1) returns 1 for odd numbers, 0 for even numbers
19 currentOddCount += number & 1;
20
21 // If we have at least k odd numbers, we can form valid subarrays
22 // Look for subarrays that would give us exactly k odd numbers
23 if (currentOddCount - k >= 0) {
24 // Add the count of previous positions that had (currentOddCount - k) odd numbers
25 // These positions can be starting points for subarrays ending at current position
26 result += oddCountFrequency[currentOddCount - k];
27 }
28
29 // Update frequency: we've now seen currentOddCount odd numbers one more time
30 oddCountFrequency[currentOddCount]++;
31 }
32
33 return result;
34 }
35}
36
1class Solution {
2public:
3 int numberOfSubarrays(vector<int>& nums, int k) {
4 int n = nums.size();
5
6 // frequency[i] stores count of prefixes with i odd numbers
7 vector<int> frequency(n + 1, 0);
8 frequency[0] = 1; // Empty prefix has 0 odd numbers
9
10 int result = 0;
11 int oddCount = 0; // Running count of odd numbers
12
13 // Iterate through each number in the array
14 for (int& num : nums) {
15 // Increment odd count if current number is odd
16 oddCount += (num & 1);
17
18 // If we have at least k odd numbers, check for valid subarrays
19 // A subarray ending here has exactly k odd numbers if
20 // there exists a prefix with (oddCount - k) odd numbers
21 if (oddCount - k >= 0) {
22 result += frequency[oddCount - k];
23 }
24
25 // Update frequency map with current odd count
26 frequency[oddCount]++;
27 }
28
29 return result;
30 }
31};
32
1function numberOfSubarrays(nums: number[], k: number): number {
2 // Get the length of the input array
3 const arrayLength = nums.length;
4
5 // Initialize a frequency map to store count of prefix sums
6 // Index represents the number of odd numbers seen so far
7 // Value represents how many times this count has occurred
8 const prefixSumCount = Array(arrayLength + 1).fill(0);
9
10 // Base case: zero odd numbers have been seen once (empty prefix)
11 prefixSumCount[0] = 1;
12
13 // Initialize variables
14 let currentOddCount = 0; // Running count of odd numbers
15 let result = 0; // Final answer - number of valid subarrays
16
17 // Iterate through each number in the array
18 for (const num of nums) {
19 // Increment odd count if current number is odd
20 // (num & 1) returns 1 if odd, 0 if even
21 currentOddCount += num & 1;
22
23 // Add the number of subarrays ending at current position
24 // that have exactly k odd numbers
25 // This happens when (currentOddCount - previousOddCount) = k
26 // So we look for previousOddCount = currentOddCount - k
27 if (currentOddCount >= k) {
28 result += prefixSumCount[currentOddCount - k];
29 }
30
31 // Update the frequency map with current odd count
32 prefixSumCount[currentOddCount] += 1;
33 }
34
35 return result;
36}
37
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the array nums
exactly once. In each iteration, it performs constant-time operations:
- Checking if a number is odd using
v & 1
-O(1)
- Accessing and updating the counter dictionary -
O(1)
on average - Arithmetic operations for updating
t
andans
-O(1)
Since we traverse all n
elements once with constant-time operations per element, the overall time complexity is O(n)
.
Space Complexity: O(n)
The space complexity is determined by the Counter
dictionary cnt
. In the worst case, when all elements in the array are odd, the variable t
(which counts odd numbers) can have n + 1
different values (from 0
to n
). This means the counter dictionary could store up to n + 1
key-value pairs. Therefore, the space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Initialize the Counter with {0: 1}
The Pitfall: A common mistake is initializing an empty Counter without the base case {0: 1}
. This causes the algorithm to miss subarrays that start from the beginning of the array.
Why it happens: When we have exactly k
odd numbers from the start of the array, we need to count this as a valid subarray. Without {0: 1}
, when odd_count = k
, looking up prefix_count[k - k] = prefix_count[0]
would return 0 instead of 1.
Example of the bug:
# WRONG - Missing subarrays starting at index 0 prefix_count = Counter() # Empty counter
Correct approach:
# CORRECT - Handles subarrays from the beginning prefix_count = Counter({0: 1})
2. Incorrect Order of Operations
The Pitfall: Updating prefix_count[odd_count]
before checking prefix_count[odd_count - k]
can lead to overcounting when k = 0
.
Why it happens: When k = 0
, we're looking for subarrays with no odd numbers. If we increment prefix_count[odd_count]
first, then check prefix_count[odd_count - 0]
, we'd be counting the current position itself as a valid subarray endpoint, which is incorrect.
Example of the bug:
# WRONG - Order matters when k = 0 for num in nums: odd_count += num & 1 prefix_count[odd_count] += 1 # Updated first result += prefix_count[odd_count - k] # Then checked - causes overcounting
Correct approach:
# CORRECT - Check first, then update for num in nums: odd_count += num & 1 result += prefix_count[odd_count - k] # Check first prefix_count[odd_count] += 1 # Then update
3. Using Wrong Method to Check Odd Numbers
The Pitfall: Using modulo operator incorrectly or with wrong precedence can give incorrect results.
Example of the bug:
# WRONG - Operator precedence issue odd_count += num % 2 == 1 # Returns True/False, not 0/1 consistently # WRONG - Doesn't handle negative numbers correctly odd_count += num % 2 # -3 % 2 = -1 in Python, not 1
Correct approaches:
# CORRECT - Bitwise AND always works
odd_count += num & 1
# CORRECT - Explicit conversion if using modulo
odd_count += 1 if num % 2 != 0 else 0
# CORRECT - Taking absolute value for modulo
odd_count += abs(num) % 2
You are given an array of intervals where intervals[i] = [start_i, end_i]
represent the start and end of the ith
interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Want a Structured Path to Master System Design Too? Don’t Miss This!