🧠
Prefix Sum + Hash Map (First Seen Remainder)
💡 This approach uses prefix sums and modular arithmetic to reduce time complexity by remembering the earliest index where a particular remainder was seen, enabling quick subarray length calculation.
Intuition
If two prefix sums have the same remainder when divided by k, the subarray between them sums to a multiple of k.
Algorithm
- Initialize a hash map to store remainder to earliest index mapping, starting with remainder 0 at index -1.
- Iterate through the array, computing prefix sum and its remainder modulo k.
- If remainder seen before, update max_length with distance between current index and earliest index of this remainder.
- If remainder not seen, store current index as earliest occurrence.
💡 The key insight is that same remainder prefix sums indicate divisible subarrays, and storing earliest indices helps find longest subarray.
def longest_subarray_div_by_k(nums, k):
remainder_index = {0: -1}
max_length = 0
prefix_sum = 0
for i, num in enumerate(nums):
prefix_sum += num
remainder = prefix_sum % k
if remainder < 0:
remainder += k
if remainder in remainder_index:
max_length = max(max_length, i - remainder_index[remainder])
else:
remainder_index[remainder] = i
return max_length
# Driver code
if __name__ == '__main__':
print(longest_subarray_div_by_k([2,7,6,1,4,5], 3)) # Output: 4
Line Notes
remainder_index = {0: -1}Store remainder 0 at index -1 to handle subarrays starting at index 0
prefix_sum += numAccumulate prefix sum as we iterate
remainder = prefix_sum % kCalculate remainder to detect divisible subarrays
if remainder in remainder_indexIf remainder seen before, subarray between indices is divisible
import java.util.*;
public class Solution {
public static int longestSubarrayDivByK(int[] nums, int k) {
Map<Integer, Integer> remainderIndex = new HashMap<>();
remainderIndex.put(0, -1);
int maxLength = 0, prefixSum = 0;
for (int i = 0; i < nums.length; i++) {
prefixSum += nums[i];
int remainder = prefixSum % k;
if (remainder < 0) remainder += k;
if (remainderIndex.containsKey(remainder)) {
maxLength = Math.max(maxLength, i - remainderIndex.get(remainder));
} else {
remainderIndex.put(remainder, i);
}
}
return maxLength;
}
public static void main(String[] args) {
System.out.println(longestSubarrayDivByK(new int[]{2,7,6,1,4,5}, 3)); // 4
}
}
Line Notes
remainderIndex.put(0, -1);Initialize remainder 0 at index -1 for subarrays starting at 0
prefixSum += nums[i];Accumulate prefix sum as we iterate
int remainder = prefixSum % k;Calculate remainder for divisibility check
if (remainderIndex.containsKey(remainder))Check if remainder seen before to find divisible subarray
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
int longestSubarrayDivByK(vector<int>& nums, int k) {
unordered_map<int, int> remainderIndex;
remainderIndex[0] = -1;
int maxLength = 0, prefixSum = 0;
for (int i = 0; i < nums.size(); i++) {
prefixSum += nums[i];
int remainder = prefixSum % k;
if (remainder < 0) remainder += k;
if (remainderIndex.find(remainder) != remainderIndex.end()) {
maxLength = max(maxLength, i - remainderIndex[remainder]);
} else {
remainderIndex[remainder] = i;
}
}
return maxLength;
}
int main() {
vector<int> nums = {2,7,6,1,4,5};
cout << longestSubarrayDivByK(nums, 3) << endl; // 4
return 0;
}
Line Notes
remainderIndex[0] = -1;Store remainder 0 at index -1 to handle subarrays from start
prefixSum += nums[i];Update prefix sum with current element
int remainder = prefixSum % k;Compute remainder for divisibility
if (remainderIndex.find(remainder) != remainderIndex.end())Check if remainder seen before to update max length
function longestSubarrayDivByK(nums, k) {
const remainderIndex = new Map();
remainderIndex.set(0, -1);
let maxLength = 0, prefixSum = 0;
for (let i = 0; i < nums.length; i++) {
prefixSum += nums[i];
let remainder = prefixSum % k;
if (remainder < 0) remainder += k;
if (remainderIndex.has(remainder)) {
maxLength = Math.max(maxLength, i - remainderIndex.get(remainder));
} else {
remainderIndex.set(remainder, i);
}
}
return maxLength;
}
// Test
console.log(longestSubarrayDivByK([2,7,6,1,4,5], 3)); // 4
Line Notes
remainderIndex.set(0, -1);Initialize remainder 0 at index -1 for subarrays starting at 0
prefixSum += nums[i];Accumulate prefix sum as we iterate
let remainder = prefixSum % k;Calculate remainder for divisibility check
if (remainderIndex.has(remainder))If remainder seen before, update max length
TimeO(n)
SpaceO(k) or O(n) in worst case
Single pass through array with constant time hash map operations, total O(n).
💡 For n=100000, this means about 100000 operations, which is efficient and scalable.
Interview Verdict: Accepted
This approach is optimal and accepted in interviews, demonstrating mastery of prefix sums and hash maps.