2845. Count of Interesting Subarrays
Problem Description
The problem provides an integer array called nums
, indexed from 0. Additionally, two integers modulo
and k
are given. The task is to count the number of subarrays considered "interesting." A subarray is defined as a contiguous non-empty sequence of elements within the array. For a subarray nums[l..r]
to be interesting, it must satisfy the condition that among its elements, the number of indices i
(where l
≤ i
≤ r
) such that nums[i] % modulo == k
must itself be congruent to k
when taken modulo modulo
, i.e., cnt % modulo == k
.
To understand better, consider these points:
- You go through the array and find all possible contiguous subarrays.
- For each subarray, you count the elements that, when divided by
modulo
, leave a remainder ofk
. - If the count of such elements in the subarray also, when divided by
modulo
, leaves a remainder ofk
, that subarray is called interesting, and you need to increase the interesting subarray count by one. - The output is the total number of interesting subarrays found this way.
Intuition
To approach this problem efficiently, without checking every possible subarray individually (which would be too time-consuming), we can use a technique from combinatorics that involves keeping track of the cumulative sums of certain conditions.
Here's the intuitive step-by-step breakdown:
- Transform the original array
nums
such that each element becomes1
if it satisfiesnums[i] % modulo == k
or0
otherwise. Let's call this arrayarr
. - Create a prefix sum array
s
such thats[i]
represents the total count of '1's from the start ofarr
to the current indexi
. This helps us to quickly calculate the total number of '1's in any subarray. - Use a hash-based data structure, like Counter in Python, to keep track of how many times each possible prefix sum modulo
modulo
has occurred. The key idea is that if the difference between the prefix sums of two indices is congruent tok
modulomodulo
, it implies the subarray between those two indices is interesting. - As we iterate through the
arr
, we add to a running sum (s
) the value of the current element. We then look up in our Counter how many times we've seen prefix sums that arek
less than the current summod modulo
. These contribute to our answer. - We update our Counter with the new running sum
mod modulo
at each index, incrementing the count since we've now encountered another subarray with that sum.
Applying these ideas, we achieve a solution that is linear with respect to the size of nums
, hence much more efficient than examining all possible subarrays individually.
Learn more about Prefix Sum patterns.
Solution Approach
The provided solution utilizes an array transformation, prefix sums, modular arithmetic, and a hash map for efficient lookups to tackle the subarray counting challenge.
-
Array Transformation: First, the code transforms the original
nums
array into a binary arrayarr
with the same length. Each element inarr
is set to1
ifnums[i] % modulo
equalsk
, otherwise, it is set to0
. This transformation simplifies the problem by converting it into a problem of counting the number of subarrays whose sum is congruent tok
modulomodulo
.arr = [int(x % modulo == k) for x in nums]
-
Using a HashMap (Counter) for Prefix Sum Lookup: The Counter is used to store the frequency of the occurrence of prefix sums modulo
modulo
. Initially, Counter is set to{0: 1}
because we start with a sum of0
and there is one way to have a sum of0
(no elements).cnt = Counter() cnt[0] = 1
-
Calculating Prefix Sums and Counting Interesting Subarrays: As we iterate through each element in the transformed array
arr
, we maintain a running sums
. For each new elementx
, the running sums
is incremented byx
, representing the sum of a prefix ending at this index.s += x
We then determine the number of interesting subarrays that end at the current index by looking up how many times we've seen prefix sums that would make the sum of the current subarray equal to
k
modulomodulo
. This is done by checkingcnt[(s - k) % modulo]
.ans += cnt[(s - k) % modulo]
After checking for interesting subarrays, we update the Counter with the new running sum modulo
modulo
to account for the new subarray ending at this index.cnt[s % modulo] += 1
-
Returning the Result: The variable
ans
is used to accumulate the count of interesting subarrays. After iterating over the arrayarr
,ans
holds the final count of all interesting subarrays, which is returned as the result.return ans
The overall time complexity of the solution is O(n), as it requires a single pass through the array, and space complexity is also O(n) due to the additional array arr
and the Counter which might store up to modulo
distinct prefix sums.
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 work through an example to illustrate the solution approach.
Consider the integer array nums = [1, 2, 3, 4, 5]
, with modulo = 2
, and k = 1
. The task is to count the number of interesting subarrays based on the given criteria.
-
Array Transformation: We transform
nums
intoarr
by setting eacharr[i]
to1
ifnums[i] % 2 == 1
, otherwise0
. Thus,arr
becomes[1, 0, 1, 0, 1]
, since1
,3
, and5
are odd numbers and give a remainder of1
when divided by2
. -
Using a HashMap (Counter) for Prefix Sum Lookup: Initialize a Counter with
{0: 1}
, representing that the sum of zero occurs once at the beginning (no subarray).cnt = Counter({0: 1})
-
Calculating Prefix Sums and Counting Interesting Subarrays: Now, we start iterating through
arr
and sketch out the process dynamically:-
For
arr[0]
, which equals1
, we increment our running sums = 0 + 1 = 1
. We then look in the Counter forcnt[(1 - 1) % 2] = cnt[0]
, which is1
, as we have seen a prefix sum (that sums to0
) exactly once before addingarr[0]
. We add1
to our answer and update the Counter tocnt[1] += 1
. -
For
arr[1]
with a value of0
, our running sum does not change (s = 1
). We look upcnt[(1 - 1) % 2] = cnt[0]
, again finding a1
. We add it to our answer (nowans = 2
) and leave the Counter unchanged sincearr[1]
is0
. -
For
arr[2] = 1
,s
is updated to2
. We checkcnt[(2 - 1) % 2] = cnt[1]
in the Counter, which is1
, so our answer increments to3
. We then incrementcnt[2 % 2] = cnt[0]
by1
.
By iterating over the entire array
arr
, we keep a running sums
, check the Counter, and update the counts of encountered modulated prefix sums, accumulating the number of interesting subarrays intoans
. -
-
Returning the Result: After iterating over the entire array
arr
, we will have the final count of all interesting subarrays stored in the variableans
. Assuming we were incrementingans
along with the iterations as described, the final result would be returned.
This approach simplifies the process, ensuring we only need to traverse the array once, giving an O(n) time complexity, which is efficient for large arrays.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:
5 # This array will contain 1s at the positions where the element
6 # in the original nums array satisfies the condition (x % modulo == k).
7 interesting_elements = [int(num % modulo == k) for num in nums]
8
9 # Counter to store the frequency of cumulative sums mod modulo
10 cumulative_sum_frequency = Counter()
11 # Initialize with 0 sum having 1 frequency as this represents empty subarray
12 cumulative_sum_frequency[0] = 1
13
14 # Initialize answer and cumulative sum
15 count_interesting_subarrays = 0
16 cumulative_sum = 0
17
18 # Iterate through the boolean array to count interesting subarrays
19 for element in interesting_elements:
20 cumulative_sum += element
21 # The current sum minus the target sum (k) mod modulo will tell us
22 # if there is a subarray ending at the current index which is interesting
23 count_interesting_subarrays += cumulative_sum_frequency[(cumulative_sum - k) % modulo]
24 # Update cumulative frequency counter
25 cumulative_sum_frequency[cumulative_sum % modulo] += 1
26
27 # Return the total count of interesting subarrays
28 return count_interesting_subarrays
29
1import java.util.List;
2import java.util.Map;
3import java.util.HashMap;
4
5class Solution {
6 // Method that counts the number of subarrays where the number of elements equal to k modulo is also k.
7 public long countInterestingSubarrays(List<Integer> nums, int modulo, int k) {
8 int totalCount = nums.size(); // Total number of elements in nums
9 int[] remainders = new int[totalCount]; // Array to store the remainders
10
11 // Populate the remainders array with 1 if nums[i] % modulo == k or with 0 otherwise
12 for (int i = 0; i < totalCount; i++) {
13 remainders[i] = nums.get(i) % modulo;
14 if (remainders[i] == k) {
15 remainders[i] = 1;
16 } else {
17 remainders[i] = 0;
18 }
19 }
20
21 Map<Integer, Integer> remainderCounts = new HashMap<>(); // Map to store the remainder frequencies
22 remainderCounts.put(0, 1); // Initialize with 0 remainder seen once
23 long interestingSubarraysCount = 0; // Variable to hold the final count of interesting subarrays
24 int sum = 0; // Variable to accumulate the sum of remainders
25
26 // Iterate over the remainders array
27 for (int remainder : remainders) {
28 sum += remainder; // Increase the sum with the current remainder
29 // Increase the count by the number of occurrences where the adjusted sum matches the expected remainder
30 interestingSubarraysCount += remainderCounts.getOrDefault((sum - k + modulo) % modulo, 0);
31 // Update the map with the current modulus of the sum and increase the count by 1 or set it to 1 if not present
32 remainderCounts.merge(sum % modulo, 1, Integer::sum);
33 }
34
35 return interestingSubarraysCount; // Return the count of interesting subarrays
36 }
37}
38
1#include <vector>
2#include <unordered_map>
3
4class Solution {
5public:
6 // Function that counts the number of "interesting" subarrays
7 // An "interesting" subarray is one where the sum of its elements, modulo 'modulo', equals 'k'
8 long long countInterestingSubarrays(std::vector<int>& nums, int modulo, int k) {
9 int n = nums.size(); // Get the size of the 'nums' vector
10 std::vector<int> modArray(n); // Array to store modulo transformations
11
12 // Preprocessing: Populate 'modArray' with 1 if nums[i] % modulo == k, otherwise 0
13 for (int i = 0; i < n; ++i) {
14 modArray[i] = (nums[i] % modulo == k) ? 1 : 0;
15 }
16
17 // Hash map to keep track of the count of prefix sums modulo 'modulo'
18 std::unordered_map<int, int> prefixCount;
19 prefixCount[0] = 1; // Initialize for the case where subarray begins at index 0
20
21 long long interestingSubarraysCount = 0; // Variable to store the count of interesting subarrays
22 int currentSumModulo = 0; // Variable to store current prefix sum modulo 'modulo'
23
24 // Iterate over the modified array to count "interesting" subarrays
25 for (int element : modArray) {
26 currentSumModulo += element; // Update current prefix sum
27 // Calculate adjusted sum for negative cases and find count in 'prefixCount'
28 interestingSubarraysCount += prefixCount[(currentSumModulo - k + modulo) % modulo];
29 // Increase the count for this sum modulo 'modulo'
30 prefixCount[currentSumModulo % modulo]++;
31 }
32
33 // Return the final count of "interesting" subarrays
34 return interestingSubarraysCount;
35 }
36};
37
1function countInterestingSubarrays(nums: number[], modulo: number, targetRemainder: number): number {
2 // Initialize an array to store binary values, 1 for integers that have a remainder equal to targetRemainder when divided by modulo and 0 otherwise.
3 const binaryRemainderArray: number[] = [];
4 for (const num of nums) {
5 binaryRemainderArray.push(num % modulo === targetRemainder ? 1 : 0);
6 }
7
8 // Create a map to count the occurrences of cumulative sums modulo 'modulo'.
9 const cumulativeSumCounts: Map<number, number> = new Map();
10 cumulativeSumCounts.set(0, 1); // Initialize with a zero sum to account for subarrays that start from index 0.
11
12 let interestingSubarraysCount = 0; // Initialize the count of interesting subarrays.
13 let cumulativeSum = 0; // Initialize the cumulative sum of binary values.
14
15 for (const binaryValue of binaryRemainderArray) {
16 // Accumulate the binary values to keep track of the number of elements equal to targetRemainder modulo 'modulo'
17 cumulativeSum += binaryValue;
18
19 // Calculate the adjusted cumulative sum for the interesting subarray.
20 const adjustedSum = (cumulativeSum - targetRemainder + modulo) % modulo;
21
22 // Add the number of occurrences where the adjusted cumulative sum has been seen before.
23 interestingSubarraysCount += cumulativeSumCounts.get(adjustedSum) || 0;
24
25 // Increment the count for the current cumulative sum.
26 cumulativeSumCounts.set(cumulativeSum % modulo, (cumulativeSumCounts.get(cumulativeSum % modulo) || 0) + 1);
27 }
28
29 return interestingSubarraysCount; // Return the total count of interesting subarrays found.
30}
31
Time and Space Complexity
The time complexity of the code is O(n)
, where n
is the length of the input list nums
. This is because the code iterates through the nums
list once, performing a constant amount of work for each element by computing the modulo, updating the sum s
, looking up and updating the count in the cnt
dictionary, and incrementing the answer ans
.
The space complexity of the code is also O(n)
due to the use of the cnt
dictionary, which stores up to n
unique sums modulo the value of modulo
, and the list arr
which stores n
elements.
Learn more about how to find time and space complexity quickly using problem constraints.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!