Before attempting this problem, you should be comfortable with:
Prefix Sums - The solution uses prefix sums to efficiently compute subarray sums
Modular Arithmetic - Understanding how remainders work, especially that two numbers with the same remainder mod k have a difference divisible by k
Hash Maps - Used to count occurrences of each remainder for efficient lookup
1. Brute Force
Intuition
The simplest approach checks every possible subarray. For each starting index, we extend the subarray one element at a time, keeping a running sum. If the sum is divisible by k (i.e., sum % k == 0), we count it.
classSolution:defsubarraysDivByK(self, nums: List[int], k:int)->int:
n, res =len(nums),0for i inrange(n):
curSum =0for j inrange(i, n):
curSum += nums[j]if curSum % k ==0:
res +=1return res
publicclassSolution{publicintsubarraysDivByK(int[] nums,int k){int n = nums.length, res =0;for(int i =0; i < n; i++){int curSum =0;for(int j = i; j < n; j++){
curSum += nums[j];if(curSum % k ==0){
res++;}}}return res;}}
classSolution{public:intsubarraysDivByK(vector<int>& nums,int k){int n = nums.size(), res =0;for(int i =0; i < n; i++){int curSum =0;for(int j = i; j < n; j++){
curSum += nums[j];if(curSum % k ==0){
res++;}}}return res;}};
classSolution{/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/subarraysDivByK(nums, k){const n = nums.length;let res =0;for(let i =0; i < n; i++){let curSum =0;for(let j = i; j < n; j++){
curSum += nums[j];if(curSum % k ===0){
res++;}}}return res;}}
publicclassSolution{publicintSubarraysDivByK(int[] nums,int k){int n = nums.Length, res =0;for(int i =0; i < n; i++){int curSum =0;for(int j = i; j < n; j++){
curSum += nums[j];if(curSum % k ==0){
res++;}}}return res;}}
funcsubarraysDivByK(nums []int, k int)int{
n, res :=len(nums),0for i :=0; i < n; i++{
curSum :=0for j := i; j < n; j++{
curSum += nums[j]if curSum%k ==0{
res++}}}return res
}
class Solution {funsubarraysDivByK(nums: IntArray, k: Int): Int {val n = nums.size
var res =0for(i in0 until n){var curSum =0for(j in i until n){
curSum += nums[j]if(curSum % k ==0){
res++}}}return res
}}
classSolution{funcsubarraysDivByK(_ nums:[Int],_ k:Int)->Int{let n = nums.count
var res =0for i in0..<n {var curSum =0for j in i..<n {
curSum += nums[j]if curSum % k ==0{
res +=1}}}return res
}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(1)
2. Prefix Sum + Hash Map
Intuition
If two prefix sums have the same remainder when divided by k, their difference is divisible by k. So the subarray between those two positions has a sum divisible by k. We use a hash map to count how many times each remainder has appeared. For each new prefix sum, we check how many previous prefix sums share the same remainder.
Algorithm
Initialize prefixSum = 0, res = 0, and a hash map prefixCnt with {0: 1}.
For each number in the array:
Add it to prefixSum.
Compute remain = prefixSum % k. Handle negative remainders by adding k if needed.
classSolution:defsubarraysDivByK(self, nums: List[int], k:int)->int:
prefix_sum =0
res =0
prefix_cnt = defaultdict(int)
prefix_cnt[0]=1for n in nums:
prefix_sum += n
remain = prefix_sum % k
res += prefix_cnt[remain]
prefix_cnt[remain]+=1return res
classSolution{public:intsubarraysDivByK(vector<int>& nums,int k){int prefixSum =0, res =0;
unordered_map<int,int> prefixCnt;
prefixCnt[0]=1;for(int n : nums){
prefixSum += n;int remain = prefixSum % k;if(remain <0) remain += k;
res += prefixCnt[remain];
prefixCnt[remain]++;}return res;}};
classSolution{/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/subarraysDivByK(nums, k){let prefixSum =0,
res =0;const prefixCnt =newMap();
prefixCnt.set(0,1);for(let n of nums){
prefixSum += n;let remain = prefixSum % k;if(remain <0) remain += k;
res += prefixCnt.get(remain)||0;
prefixCnt.set(remain,(prefixCnt.get(remain)||0)+1);}return res;}}
publicclassSolution{publicintSubarraysDivByK(int[] nums,int k){int prefixSum =0, res =0;Dictionary<int,int> prefixCnt =newDictionary<int,int>();
prefixCnt[0]=1;foreach(int n in nums){
prefixSum += n;int remain = prefixSum % k;if(remain <0) remain += k;if(prefixCnt.ContainsKey(remain)){
res += prefixCnt[remain];
prefixCnt[remain]++;}else{
prefixCnt[remain]=1;}}return res;}}
funcsubarraysDivByK(nums []int, k int)int{
prefixSum, res :=0,0
prefixCnt :=make(map[int]int)
prefixCnt[0]=1for_, n :=range nums {
prefixSum += n
remain := prefixSum % k
if remain <0{
remain += k
}
res += prefixCnt[remain]
prefixCnt[remain]++}return res
}
class Solution {funsubarraysDivByK(nums: IntArray, k: Int): Int {var prefixSum =0var res =0val prefixCnt = HashMap<Int, Int>()
prefixCnt[0]=1for(n in nums){
prefixSum += n
var remain = prefixSum % k
if(remain <0) remain += k
res += prefixCnt.getOrDefault(remain,0)
prefixCnt[remain]= prefixCnt.getOrDefault(remain,0)+1}return res
}}
classSolution{funcsubarraysDivByK(_ nums:[Int],_ k:Int)->Int{var prefixSum =0var res =0var prefixCnt:[Int:Int]=[0:1]for n in nums {
prefixSum += n
var remain = prefixSum % k
if remain <0{ remain += k }
res += prefixCnt[remain,default:0]
prefixCnt[remain,default:0]+=1}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(k)
3. Prefix Sum + Array
Intuition
Since remainders when dividing by k are always in the range [0, k-1], we can use a fixed-size array instead of a hash map. This gives slightly better constant factors. We also handle negative numbers by adding k to the modulo result, ensuring all remainders are non-negative.
Algorithm
Create an array count of size k, initialized to zeros, with count[0] = 1.
Initialize prefix = 0 and res = 0.
For each number in the array:
Update prefix = (prefix + num % k + k) % k to handle negatives.
classSolution:defsubarraysDivByK(self, nums: List[int], k:int)->int:
count =[0]* k
count[0]=1
prefix = res =0for num in nums:
prefix =(prefix + num + k)% k
res += count[prefix]
count[prefix]+=1return res
publicclassSolution{publicintsubarraysDivByK(int[] nums,int k){int[] count =newint[k];
count[0]=1;int prefix =0, res =0;for(int num : nums){
prefix =(prefix + num % k + k)% k;
res += count[prefix];
count[prefix]++;}return res;}}
classSolution{public:intsubarraysDivByK(vector<int>& nums,int k){
vector<int>count(k,0);
count[0]=1;int prefix =0, res =0;for(int num : nums){
prefix =(prefix + num % k + k)% k;
res += count[prefix];
count[prefix]++;}return res;}};
classSolution{/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/subarraysDivByK(nums, k){const count =Array(k).fill(0);
count[0]=1;let prefix =0,
res =0;for(let num of nums){
prefix =(prefix +(num % k)+ k)% k;
res += count[prefix];
count[prefix]++;}return res;}}
publicclassSolution{publicintSubarraysDivByK(int[] nums,int k){int[] count =newint[k];
count[0]=1;int prefix =0, res =0;foreach(int num in nums){
prefix =(prefix + num % k + k)% k;
res += count[prefix];
count[prefix]++;}return res;}}
funcsubarraysDivByK(nums []int, k int)int{
count :=make([]int, k)
count[0]=1
prefix, res :=0,0for_, num :=range nums {
prefix =(prefix + num%k + k)% k
res += count[prefix]
count[prefix]++}return res
}
class Solution {funsubarraysDivByK(nums: IntArray, k: Int): Int {val count =IntArray(k)
count[0]=1var prefix =0var res =0for(num in nums){
prefix =(prefix + num % k + k)% k
res += count[prefix]
count[prefix]++}return res
}}
classSolution{funcsubarraysDivByK(_ nums:[Int],_ k:Int)->Int{var count =[Int](repeating:0, count: k)
count[0]=1varprefix=0var res =0for num in nums {prefix=(prefix+ num % k + k)% k
res += count[prefix]
count[prefix]+=1}return res
}}
Time & Space Complexity
Time complexity: O(n+k)
Space complexity: O(k)
Common Pitfalls
Mishandling Negative Remainders
In most programming languages, the modulo of a negative number can return a negative result (e.g., -5 % 3 = -2 in Java/C++). You must normalize the remainder to be non-negative by using ((prefixSum % k) + k) % k to ensure correct hash map lookups.
Confusing This Problem with Subarray Sum Equals K
While both problems use prefix sums, the comparison logic differs. Here you need to match remainders, not exact differences. Two prefix sums with the same remainder modulo k indicate a valid subarray, regardless of their absolute values.
Forgetting to Initialize Count for Remainder Zero
The hash map or array must start with count[0] = 1 to account for subarrays starting from index 0 that are directly divisible by k. Missing this initialization causes undercounting.