2845. Count of Interesting Subarrays
Problem Description
You are given a 0-indexed integer array nums
, an integer modulo
, and an integer k
.
Your task is to find the count of subarrays that are interesting.
A subarray nums[l..r]
is considered interesting if it satisfies the following condition:
- Count how many indices
i
in the range[l, r]
have the property thatnums[i] % modulo == k
. Let's call this countcnt
. - The subarray is interesting if
cnt % modulo == k
.
In other words, you need to:
- For each subarray, count how many elements in that subarray leave remainder
k
when divided bymodulo
- Check if this count itself leaves remainder
k
when divided bymodulo
- If yes, the subarray is interesting
Return the total number of interesting subarrays in the array.
Note: A subarray is a contiguous non-empty sequence of elements within an array.
Example to clarify:
If nums = [3, 2, 4]
, modulo = 2
, and k = 1
:
- For subarray
[3]
: one element where3 % 2 = 1
(equalsk
), socnt = 1
. Since1 % 2 = 1
(equalsk
), this subarray is interesting. - For subarray
[3, 2]
: only one element where3 % 2 = 1
, socnt = 1
. Since1 % 2 = 1
, this subarray is interesting. - For subarray
[2, 4]
: zero elements satisfy the condition, socnt = 0
. Since0 % 2 = 0
(not equal tok
), this subarray is not interesting.
Intuition
The key insight is to transform this problem into a simpler one using prefix sums and modular arithmetic.
First, let's simplify what we're counting. Instead of working with the original array, we can create a binary array where each position is 1
if nums[i] % modulo == k
, and 0
otherwise. Now the problem becomes: find subarrays where the sum of elements (count of 1s) satisfies sum % modulo == k
.
For any subarray [l, r]
, the count of 1s equals prefix_sum[r] - prefix_sum[l-1]
. We want this difference to satisfy:
(prefix_sum[r] - prefix_sum[l-1]) % modulo == k
Rearranging this equation using modular arithmetic:
prefix_sum[r] % modulo - prefix_sum[l-1] % modulo ≡ k (mod modulo)
This means:
prefix_sum[r] % modulo ≡ (prefix_sum[l-1] % modulo + k) % modulo
Or equivalently:
prefix_sum[l-1] % modulo ≡ (prefix_sum[r] % modulo - k) % modulo
This reveals the pattern: for each position r
, we need to count how many previous positions l-1
have a prefix sum that, when taken modulo modulo
, equals (current_prefix_sum - k) % modulo
.
This naturally leads to using a hash table to track the frequency of each prefix_sum % modulo
value as we traverse the array. For each position, we:
- Check how many previous prefix sums would make a valid subarray ending here
- Add the current prefix sum modulo value to our frequency map
We initialize with cnt[0] = 1
to handle subarrays starting from index 0, where the "previous" prefix sum is considered to be 0.
Learn more about Prefix Sum patterns.
Solution Approach
The implementation follows the prefix sum with hash table pattern:
Step 1: Transform the array
arr = [int(x % modulo == k) for x in nums]
We create a binary array arr
where arr[i] = 1
if nums[i] % modulo == k
, otherwise arr[i] = 0
. This transformation simplifies our problem to counting subarrays with a specific sum property.
Step 2: Initialize the hash table
cnt = Counter() cnt[0] = 1
We use a hash table cnt
to track the frequency of prefix_sum % modulo
values. Initialize cnt[0] = 1
to handle subarrays that start from index 0.
Step 3: Traverse and count
ans = s = 0 for x in arr: s += x ans += cnt[(s - k) % modulo] cnt[s % modulo] += 1
For each element in the transformed array:
- Update the prefix sum
s
by adding the current element - Look up how many previous prefix sums would create a valid subarray ending at the current position. We need prefix sums that satisfy
(s - prefix_sum) % modulo == k
, which means we look forprefix_sum % modulo == (s - k) % modulo
- Add this count to our answer
- Update the hash table with the current prefix sum modulo value for future positions to use
The key insight is that (s - k) % modulo
gives us the required remainder that previous prefix sums must have for the subarray to be interesting.
Time Complexity: O(n)
where n
is the length of the array, as we traverse once.
Space Complexity: O(min(n, modulo))
for the hash table, which stores at most modulo
different remainder values.
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 walk through with nums = [3, 2, 4]
, modulo = 2
, k = 1
.
Step 1: Transform the array
- Check each element: does
nums[i] % 2 == 1
? 3 % 2 = 1
✓,2 % 2 = 0
✗,4 % 2 = 0
✗- Transformed array:
arr = [1, 0, 0]
Step 2: Initialize
cnt = {0: 1}
(to handle subarrays from start)ans = 0
,s = 0
(prefix sum)
Step 3: Process each element
Index 0 (arr[0] = 1):
- Update prefix sum:
s = 0 + 1 = 1
- Look for previous prefix sums with remainder
(1 - 1) % 2 = 0
cnt[0] = 1
, so add 1 to answer:ans = 1
- Update cnt:
cnt[1 % 2] = cnt[1] += 1
- State:
cnt = {0: 1, 1: 1}
,ans = 1
Index 1 (arr[1] = 0):
- Update prefix sum:
s = 1 + 0 = 1
- Look for previous prefix sums with remainder
(1 - 1) % 2 = 0
cnt[0] = 1
, so add 1 to answer:ans = 2
- Update cnt:
cnt[1 % 2] = cnt[1] += 1
- State:
cnt = {0: 1, 1: 2}
,ans = 2
Index 2 (arr[2] = 0):
- Update prefix sum:
s = 1 + 0 = 1
- Look for previous prefix sums with remainder
(1 - 1) % 2 = 0
cnt[0] = 1
, so add 1 to answer:ans = 3
- Update cnt:
cnt[1 % 2] = cnt[1] += 1
- State:
cnt = {0: 1, 1: 3}
,ans = 3
Result: 3 interesting subarrays
Verification:
[3]
(indices 0-0): cnt=1,1 % 2 = 1
✓[3,2]
(indices 0-1): cnt=1,1 % 2 = 1
✓[3,2,4]
(indices 0-2): cnt=1,1 % 2 = 1
✓
Solution Implementation
1class Solution:
2 def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:
3 # Convert nums to binary array where 1 indicates element satisfies (num % modulo == k)
4 # This transforms the problem to counting subarrays with specific sum properties
5 satisfies_condition = [1 if num % modulo == k else 0 for num in nums]
6
7 # Dictionary to store frequency of prefix sums modulo 'modulo'
8 # Key: prefix sum % modulo, Value: count of such prefix sums
9 prefix_sum_count = Counter()
10
11 # Initialize with 0 to handle subarrays starting from index 0
12 # This represents the empty prefix with sum 0
13 prefix_sum_count[0] = 1
14
15 # Initialize result counter and running prefix sum
16 result = 0
17 current_prefix_sum = 0
18
19 # Iterate through the binary array
20 for value in satisfies_condition:
21 # Update the running prefix sum
22 current_prefix_sum += value
23
24 # For a subarray to be interesting, we need:
25 # (current_prefix_sum - previous_prefix_sum) % modulo == k
26 # Which means: previous_prefix_sum % modulo == (current_prefix_sum - k) % modulo
27 # Count all valid previous prefix sums that can form an interesting subarray
28 target_remainder = (current_prefix_sum - k) % modulo
29 result += prefix_sum_count[target_remainder]
30
31 # Add current prefix sum to the dictionary for future subarrays
32 prefix_sum_count[current_prefix_sum % modulo] += 1
33
34 return result
35
1class Solution {
2 public long countInterestingSubarrays(List<Integer> nums, int modulo, int k) {
3 int n = nums.size();
4
5 // Convert input list to binary array where 1 indicates the element satisfies nums[i] % modulo == k
6 int[] satisfiesCondition = new int[n];
7 for (int i = 0; i < n; i++) {
8 satisfiesCondition[i] = (nums.get(i) % modulo == k) ? 1 : 0;
9 }
10
11 // Map to store frequency of prefix sum remainders when divided by modulo
12 // Key: remainder of prefix sum divided by modulo
13 // Value: count of such remainders seen so far
14 Map<Integer, Integer> prefixSumRemainderCount = new HashMap<>();
15
16 // Initialize with 0 remainder having count 1 (empty prefix)
17 prefixSumRemainderCount.put(0, 1);
18
19 long result = 0;
20 int prefixSum = 0;
21
22 // Iterate through the binary array
23 for (int currentValue : satisfiesCondition) {
24 // Update running prefix sum
25 prefixSum += currentValue;
26
27 // Calculate the target remainder we need to find
28 // We want subarrays where (prefixSum[j] - prefixSum[i]) % modulo == k
29 // This means prefixSum[j] % modulo - prefixSum[i] % modulo == k (mod modulo)
30 // So we look for prefixSum[i] % modulo == (prefixSum[j] - k) % modulo
31 int targetRemainder = ((prefixSum - k) % modulo + modulo) % modulo;
32
33 // Add count of all valid subarrays ending at current position
34 result += prefixSumRemainderCount.getOrDefault(targetRemainder, 0);
35
36 // Update the map with current prefix sum remainder
37 int currentRemainder = prefixSum % modulo;
38 prefixSumRemainderCount.merge(currentRemainder, 1, Integer::sum);
39 }
40
41 return result;
42 }
43}
44
1class Solution {
2public:
3 long long countInterestingSubarrays(vector<int>& nums, int modulo, int k) {
4 int n = nums.size();
5
6 // Convert array to binary representation: 1 if nums[i] % modulo == k, 0 otherwise
7 vector<int> satisfiesCondition(n);
8 for (int i = 0; i < n; ++i) {
9 satisfiesCondition[i] = (nums[i] % modulo == k) ? 1 : 0;
10 }
11
12 // Map to store frequency of prefix sum remainders when divided by modulo
13 // Key: remainder of prefix sum when divided by modulo
14 // Value: count of such prefix sums
15 unordered_map<int, int> prefixSumModCount;
16 prefixSumModCount[0] = 1; // Empty prefix has sum 0
17
18 long long result = 0;
19 int prefixSum = 0;
20
21 // Iterate through the binary array
22 for (int currentValue : satisfiesCondition) {
23 prefixSum += currentValue;
24
25 // To find subarrays where (count % modulo == k), we need:
26 // (prefixSum[j] - prefixSum[i-1]) % modulo == k
27 // This means: prefixSum[i-1] % modulo == (prefixSum[j] - k) % modulo
28 // We look for previous prefix sums with the required remainder
29 int targetRemainder = ((prefixSum - k) % modulo + modulo) % modulo;
30 result += prefixSumModCount[targetRemainder];
31
32 // Update the count for current prefix sum's remainder
33 prefixSumModCount[prefixSum % modulo]++;
34 }
35
36 return result;
37 }
38};
39
1/**
2 * Counts the number of interesting subarrays where the count of elements
3 * satisfying (nums[i] % modulo === k) has a count c such that (c % modulo === k)
4 * @param nums - The input array of numbers
5 * @param modulo - The modulo value for calculations
6 * @param k - The target remainder value
7 * @returns The count of interesting subarrays
8 */
9function countInterestingSubarrays(nums: number[], modulo: number, k: number): number {
10 // Transform the array: 1 if element satisfies condition, 0 otherwise
11 const transformedArray: number[] = [];
12 for (const num of nums) {
13 transformedArray.push(num % modulo === k ? 1 : 0);
14 }
15
16 // Map to store frequency of prefix sum remainders
17 // Key: prefix sum % modulo, Value: frequency count
18 const prefixSumModCount: Map<number, number> = new Map();
19 prefixSumModCount.set(0, 1); // Initialize with 0 remainder having count 1
20
21 let interestingSubarrayCount: number = 0;
22 let currentPrefixSum: number = 0;
23
24 // Iterate through transformed array to find valid subarrays
25 for (const value of transformedArray) {
26 currentPrefixSum += value;
27
28 // Check if there exists a previous prefix sum that forms a valid subarray
29 // We need (currentSum - previousSum) % modulo === k
30 // Which means previousSum % modulo === (currentSum - k) % modulo
31 const targetRemainder: number = (currentPrefixSum - k + modulo) % modulo;
32 interestingSubarrayCount += prefixSumModCount.get(targetRemainder) || 0;
33
34 // Update the frequency map with current prefix sum remainder
35 const currentRemainder: number = currentPrefixSum % modulo;
36 prefixSumModCount.set(currentRemainder, (prefixSumModCount.get(currentRemainder) || 0) + 1);
37 }
38
39 return interestingSubarrayCount;
40}
41
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. This is because:
- Creating the
arr
list requires a single pass throughnums
withO(n)
time - The main loop iterates through each element in
arr
exactly once, which isO(n)
- Inside the loop, all operations (addition, dictionary lookup, dictionary insertion, modulo operation) are
O(1)
- Therefore, the overall time complexity is
O(n)
The space complexity is O(n)
. This is due to:
- The
arr
list storesn
elements, requiringO(n)
space - The
cnt
Counter (dictionary) can store at mostmin(n+1, modulo)
distinct keys, since we're storing values modulomodulo
. In the worst case, this isO(n)
whenmodulo >= n+1
- Other variables (
ans
,s
,x
) useO(1)
space - Therefore, the overall space complexity is
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Modulo Operation for Negative Numbers
The most critical pitfall in this solution is handling the modulo operation when computing (current_prefix_sum - k) % modulo
. In Python, the modulo operation handles negative numbers correctly (always returns non-negative results), but this behavior varies across programming languages.
Problem Example:
- If
current_prefix_sum = 1
,k = 3
,modulo = 5
(1 - 3) % 5
should give us3
(the correct mathematical result)- In Python:
(-2) % 5 = 3
✓ - In Java/C++:
(-2) % 5 = -2
✗
Solution: For languages that don't handle negative modulo correctly, use:
target_remainder = ((current_prefix_sum - k) % modulo + modulo) % modulo
2. Forgetting to Initialize the Hash Table with cnt[0] = 1
Without initializing cnt[0] = 1
, subarrays starting from index 0 won't be counted correctly.
Problem Example:
nums = [3]
,modulo = 2
,k = 1
- The entire array
[3]
should be counted as interesting - Without
cnt[0] = 1
, this subarray would be missed
Solution:
Always initialize the counter with cnt[0] = 1
before processing any elements.
3. Using the Wrong Target Calculation
A common mistake is looking for (s + k) % modulo
instead of (s - k) % modulo
.
Problem Example:
The relationship is: (current_sum - previous_sum) % modulo == k
This rearranges to: previous_sum % modulo == (current_sum - k) % modulo
Using addition instead would look for the wrong prefix sums and produce incorrect results.
Solution: Remember the mathematical relationship: we're looking for prefix sums where the difference equals k modulo modulo, so we subtract k from the current sum.
4. Updating Hash Table Before Counting
If you update cnt[current_prefix_sum % modulo]
before checking cnt[(current_prefix_sum - k) % modulo]
, you might incorrectly count single-element subarrays when k == 0
.
Problem Example:
- When
k = 0
and the current element satisfies the condition - If we update first, we'd count the current position as both start and end of a subarray
Solution: Always follow the order:
- Count valid subarrays using current state
- Update the hash table with current prefix sum
Is the following code DFS or BFS?
void search(Node root) { if (!root) return; visit(root); root.visited = true; for (Node node in root.adjacent) { if (!node.visited) { search(node); } } }
Recommended Readings
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
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!