Subarray Sums Divisible by K
MedPrompt
Given an integer array nums and an integer k, return the number of non-empty subarrays that have a sum divisible by k.
A subarray is a contiguous part of an array.
Example 1:
Input: nums = [4,5,0,-2,-3,1], k = 5
Output: 7
Explanation: There are 7 subarrays with a sum divisible by k = 5:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]Example 2:
Input: nums = [5], k = 9
Output: 0
Constraints:
1 <= nums.length <= 3 * 104-104 <= nums[i] <= 1042 <= k <= 104
Approaches
2 approaches with complexity analysis and trade-offs.
The most straightforward approach is to generate every possible contiguous subarray, calculate the sum of its elements, and then check if that sum is divisible by k. If it is, we increment a counter. This method is easy to conceptualize but computationally expensive.
Algorithm
- Initialize a counter
countto 0. - Use a nested loop structure to iterate through all possible subarrays.
- The outer loop with index
idetermines the start of the subarray. - The inner loop with index
jdetermines the end of the subarray.
- The outer loop with index
- For each subarray defined by
iandj, calculate its sum.- Initialize a
currentSumto 0 before the inner loop. - In the inner loop, accumulate the sum by adding
nums[j]tocurrentSum.
- Initialize a
- Check if the
currentSumis divisible byk(i.e.,currentSum % k == 0). - If the condition is met, increment the
count. - After all subarrays have been checked, return the final
count.
Walkthrough
This approach uses two nested loops to define the boundaries of each subarray. The outer loop iterates from the first element to the last, fixing the starting point of the subarray. The inner loop then iterates from this starting point to the end of the array, defining the ending point. For each subarray generated, we maintain a running sum. After adding each new element to the subarray, we check if the current running sum is divisible by k. We keep a total count of all such subarrays found.
class Solution { public int subarraysDivByK(int[] nums, int k) { int count = 0; for (int i = 0; i < nums.length; i++) { int currentSum = 0; for (int j = i; j < nums.length; j++) { currentSum += nums[j]; if (currentSum % k == 0) { count++; } } } return count; }}Complexity
Time
O(N^2), where N is the number of elements in the `nums` array. The two nested loops lead to a quadratic number of operations as we calculate the sum for each of the N*(N+1)/2 subarrays.
Space
O(1), as we only use a constant amount of extra space for variables like `count` and `currentSum`.
Trade-offs
Pros
Simple to understand and implement.
Requires no extra space other than a few variables.
Cons
Highly inefficient for larger arrays.
Will likely result in a 'Time Limit Exceeded' (TLE) error on most online judges for the given constraints.
A highly efficient approach can be devised using prefix sums and modular arithmetic. The core idea is that if two prefix sums have the same remainder when divided by k, the sum of the elements between them is divisible by k. We can find these pairs in a single pass through the array.
Algorithm
- Initialize
count = 0,prefixSum = 0. - Create a frequency array,
remainderFreq, of sizekand initialize all its elements to 0. - Set
remainderFreq[0] = 1. This is a crucial base case to account for subarrays that start from index 0 and have a sum divisible byk. - Iterate through each number
numin the input arraynums:- Add the current number to
prefixSum:prefixSum += num. - Calculate the remainder of the
prefixSumwith respect tok. To handle potential negative results from the modulo operator (e.g., in Java), use the formularemainder = (prefixSum % k + k) % k. - If this
remainderhas been seen before, it means there areremainderFreq[remainder]previous prefix sums that can form a valid subarray with the current one. Add this frequency to thecount:count += remainderFreq[remainder]. - Increment the frequency of the current
remainderin the map:remainderFreq[remainder]++.
- Add the current number to
- Return
count.
Walkthrough
Let prefixSum[i] be the sum of elements from nums[0] to nums[i]. The sum of a subarray nums[i...j] is prefixSum[j] - prefixSum[i-1]. We are looking for cases where (prefixSum[j] - prefixSum[i-1]) % k == 0. This is equivalent to prefixSum[j] % k == prefixSum[i-1] % k.
This transforms the problem into finding pairs of prefix sums that have the same remainder when divided by k. We can solve this by iterating through the array once, calculating the running prefix sum, and using a hash map or an array to store the frequencies of the remainders encountered so far.
For each element, we calculate the current prefix sum's remainder. If we have seen this remainder f times before, it means we can form f new subarrays ending at the current position whose sums are divisible by k. We add f to our total count and then update the frequency of the current remainder.
class Solution { public int subarraysDivByK(int[] nums, int k) { // Use an array to store frequencies of remainders. Size k for remainders 0 to k-1. int[] remainderFreq = new int[k]; // A prefix sum of 0 (before starting) has a remainder of 0. This is our base case. remainderFreq[0] = 1; int count = 0; int prefixSum = 0; for (int num : nums) { prefixSum += num; // Calculate remainder, handling negative results from Java's % operator. int remainder = (prefixSum % k + k) % k; // Add the number of times we've seen this remainder before to our count. count += remainderFreq[remainder]; // Increment the frequency of the current remainder. remainderFreq[remainder]++; } return count; }}Complexity
Time
O(N), where N is the number of elements in the `nums` array. We only need to iterate through the array once.
Space
O(k), where `k` is the divisor. We use an auxiliary array of size `k` to store the frequencies of the remainders. Since `k` is at most 10^4, this is considered efficient.
Trade-offs
Pros
Optimal time complexity.
Efficiently handles large inputs within the given constraints.
Cons
Requires understanding of modular arithmetic and the prefix sum technique.
Uses extra space proportional to
k.
Solutions
Solution
class Solution {public int subarraysDivByK(int[] nums, int k) { Map<Integer, Integer> cnt = new HashMap<>(); cnt.put(0, 1); int ans = 0, s = 0; for (int x : nums) { s = ((s + x) % k + k) % k; ans += cnt.getOrDefault(s, 0); cnt.merge(s, 1, Integer : : sum); } return ans; }}class Solution {public: int subarraysDivByK(vector<int> &nums, int k) { unordered_map<int, int> cnt{{0, 1}}; int ans = 0, s = 0; for (int &x : nums) { s = ((s + x) % k + k) % k; ans += cnt[s]++; } return ans; }};class Solution: def subarraysDivByK(self, nums: List[int], k: int) -> int: cnt = Counter({0: 1}) ans = s = 0 for x in nums: s = (s + x) % k ans += cnt[s] cnt[s] += 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.