Count Number of Nice Subarrays
Problem
Given an integer array nums and an integer k, return the number of contiguous subarrays that have exactly k odd numbers.
- 1 ≤ nums.length ≤ 10⁵
- 1 ≤ nums[i] ≤ 10⁵
- 1 ≤ k ≤ nums.length
Example
nums = [1,1,2,1,1], k = 32The contiguous subarrays with exactly 3 odd numbers are [1,1,2,1] (indices 0–3) and [1,2,1,1] (indices 1–4). We keep a running count curr of how many odd numbers we have seen so far and a map counts that stores how many times each curr value has appeared. counts[0] = 1 at the start represents the empty prefix before the array, so subarrays that start at index 0 can be counted. Start with curr = 0, ans = 0, counts = {0: 1}. Index 0: num = 1 (odd), so curr becomes 1. curr - k = 1 - 3 = -2, which is not in counts, so ans stays 0. We record this prefix by setting counts[1] = 1. Index 1: num = 1 (odd), so curr becomes 2. curr - k = 2 - 3 = -1, which is still not in counts, so there is no subarray with 3 odds ending here. We update counts[2] = 1. Index 2: num = 2 (even), so curr stays 2. curr - k is still -1, so ans remains 0. We have now seen prefix sum 2 twice, so counts[2] = 2. Index 3: num = 1 (odd), so curr becomes 3. Now the key step happens: curr - k = 3 - 3 = 0, and counts[0] = 1. That means there is 1 previous prefix where the odd count was 0, so the subarray from index 0 to 3 ([1,1,2,1]) has exactly 3 odd numbers. We add counts[0] to ans, so ans becomes 1, and set counts[3] = 1. Index 4: num = 1 (odd), so curr becomes 4. Now curr - k = 4 - 3 = 1, and counts[1] = 1, so there is 1 previous prefix where the odd count was 1. That gives the subarray from index 1 to 4 ([1,2,1,1]) with exactly 3 odd numbers. We add counts[1] to ans, so ans becomes 2, and set counts[4] = 1. At the end, ans = 2, which is the number of subarrays that contain exactly 3 odd numbers.
Approach
Straightforward Solution
A brute-force approach would check every subarray and count odd numbers, resulting in O(n^2) time complexity, which is too slow for large inputs.
Core Observation
The problem reduces to counting subarrays where the difference between prefix sums of odd counts equals k. Each prefix sum represents how many odd numbers have been encountered up to the current index.
Path to Optimal
Recognizing that the count of odd numbers up to each index forms a prefix sum array, the problem becomes finding pairs of prefix sums where their difference is k. Using a hash map to store frequencies of prefix sums allows constant-time lookups, enabling a single pass solution.
Optimal Approach
PreviewIterate through nums, updating the current prefix sum of odd counts. For each prefix sum curr, add counts[curr - k] to the answer, since each occurrence of curr - k represents a subarray ending at the current index with exactly k odd numbers…
Full step-by-step walkthrough on Pro →
Want the full reasoning chain?
Unlock the complete walkthrough, line-by-line analysis, and recall drill.
Unlock ProTime
O(n)
The algorithm iterates through the array once, performing constant-time hash map operations for each element.
Space
O(n)
The hash map stores frequencies of prefix sums, which in the worst case can be up to n distinct values.
Pattern Spotlight
Hash Maps (Prefix Sum Frequency Counting)
When counting subarrays with a fixed sum or count, transform the problem into prefix sums and use a hash map to track frequencies of prefix sums, enabling O(1) lookups of complementary sums that form valid subarrays.
Solution
| 1 | from collections import defaultdict
|
| 2 |
|
| 3 | class Solution:
|
| 4 | def numberOfSubarrays(self, nums: List[int], k: int) -> int:
|
| 5 | counts = defaultdict(int)
|
| 6 | counts[0] = 1
|
| 7 | ans = curr = 0
|
| 8 |
|
| 9 | for num in nums:
|
| 10 | curr += num % 2
|
| 11 | ans += counts[curr - k]
|
| 12 | counts[curr] += 1
|
| 13 |
|
| 14 | return ans |
Step-by-Step Solution
Initialize Prefix Sum Frequency Map and Counters
| 5 | counts = defaultdict(int)
|
| 6 | counts[0] = 1
|
| 7 | ans = curr = 0
|
Objective
To set up a hash map to count frequencies of prefix sums and initialize counters for the answer and current prefix sum.
Key Insight
Starting counts[0] = 1 accounts for the empty prefix before processing any elements, enabling detection of subarrays starting at index 0. Initializing ans and curr to zero prepares for accumulation of results and tracking the running count of odd numbers.
Interview Quick-Check
Core Logic
counts[0] = 1 is critical to count subarrays that start from the beginning of the array.
State & Boundaries
Initialize ans and curr to zero to represent no subarrays counted and zero odd numbers seen initially.
Iterate Through nums to Count Valid Subarrays Using Prefix Sums
To traverse the array, update the prefix sum of odd numbers, and accumulate the count of valid subarrays ending at each position.
Return the Total Count of Nice Subarrays
To return the accumulated count of subarrays with exactly k odd numbers after processing the entire array.
2 more steps with full analysis available on Pro.
Line Analysis
This solution has 3 Critical lines interviewers watch for.
counts[0] = 1
Set the frequency of prefix sum zero to one.
This initialization accounts for the empty prefix before processing any elements, allowing subarrays starting at index 0 to be counted correctly.
ans += counts[curr - k]
Add the count of prefix sums equal to curr - k to the answer.
This line is the core of the algorithm: it counts all subarrays ending at the current index that have exactly k odd numbers by leveraging the prefix sum difference property.
curr += num % 2
Increment curr by 1 if the current number is odd, else by 0.
This line updates the prefix sum of odd numbers, which is the fundamental state used to identify subarrays with exactly k odd numbers.
Full line-by-line criticality + rationale for all 8 lines available on Pro.
Test Your Understanding
Why does checking counts[curr - k] give the number of subarrays ending at the current index with exactly k odd numbers?
See the answer with Pro.
Related Problems
Hash Maps pattern
Don't just read it. Drill it.
Reconstruct Count Number of Nice Subarrays from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.
Unlock the Count Number of Nice Subarrays drill