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] <= 104
2 <= k <= 104
This problem asks to find the number of non-empty subarrays whose sum is divisible by a given integer k
. The most efficient solution uses a combination of prefix sums and a hash table (or dictionary).
Core Idea:
The key insight is that if the sum of a subarray nums[i:j]
(inclusive) is divisible by k
, then (prefix_sum[j] - prefix_sum[i-1]) % k == 0
. This implies that prefix_sum[j] % k == prefix_sum[i-1] % k
. Therefore, we only need to track the remainders of prefix sums when divided by k
.
Algorithm:
Initialization:
cnt
to store the frequency of each remainder of the prefix sum. Initialize cnt[0] = 1
because an empty subarray (which has a sum of 0) is implicitly divisible by k
.ans = 0
to store the count of subarrays with sums divisible by k
.s = 0
to represent the current prefix sum.Iteration:
nums
.x
:
s = (s + x) % k
. We use the modulo operator (%
) to keep track of only the remainder. We also handle negative remainders by adding k
and taking the modulo again, ensuring non-negative remainders.ans += cnt[s]
. This adds to ans
the number of times the current remainder s
has appeared as a prefix sum remainder before. This means we've found a subarray ending at the current position whose sum is divisible by k
.cnt[s] += 1
. Increment the count of the current remainder in the hash table.Return:
ans
.Time Complexity: O(n), where n is the length of the input array. We iterate through the array once. Hash table lookups and insertions are considered O(1) on average.
Space Complexity: O(k), in the worst case, the hash table might store remainders from 0 to k-1. In practice, it's often much less than k.
Code Examples:
The provided code examples in Python, Java, C++, Go, and TypeScript all implement this algorithm. They differ slightly in syntax and handling of hash table operations, but the core logic remains the same. The key aspects to note are:
((s + x) % k + k) % k
ensures that the remainder is always non-negative.This approach is significantly more efficient than brute-force methods (which would have O(n^2) time complexity) because it cleverly uses the properties of prefix sums and modular arithmetic to reduce the time complexity to linear.