LeetCode 1590 - Make Sum Divisible by P
This problem asks us to determine the smallest contiguous subarray we can remove from a given array of positive integers
Difficulty: 🟡 Medium
Topics: Array, Hash Table, Prefix Sum
Solution
Problem Understanding
This problem asks us to determine the smallest contiguous subarray we can remove from a given array of positive integers, nums, so that the sum of the remaining elements becomes divisible by a given integer p. The key here is that the subarray must be contiguous, and it cannot be the entire array. If the sum of the array is already divisible by p, then we do not need to remove anything, and the answer is zero. If it is impossible to make the sum divisible by p by removing a subarray, the function should return -1.
The input nums can be very large, up to 100,000 elements, and each number in the array can be as large as 10^9. Similarly, p can be as large as 10^9. This tells us that a brute-force approach that examines all possible subarrays will be too slow due to the O(n^2) or O(n^3) time complexity that could result from nested loops.
Important edge cases include when the sum is already divisible by p, when no subarray can make the sum divisible, when removing a single element is sufficient, and when the subarray to remove is at the start or end of the array.
Approaches
The brute-force approach involves iterating through all possible subarrays, calculating the sum of the remaining elements after removing each subarray, and checking divisibility by p. While this approach is straightforward and guarantees correctness, it is highly inefficient for large arrays because it would require O(n^2) or O(n^3) operations, which is not feasible for n up to 10^5.
The optimal approach uses prefix sums combined with a hash map to track remainders modulo p. The key insight is that we only need to consider subarrays whose sum modulo p matches a specific value derived from the total sum modulo p. Let the total sum of the array modulo p be total_mod = sum(nums) % p. To remove a subarray and make the sum divisible by p, the sum of the subarray modulo p must equal total_mod. By maintaining a hash map of prefix sums modulo p to their latest index, we can efficiently determine the length of the subarray to remove in O(n) time.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^2) | O(1) | Checks every subarray; too slow for large n |
| Optimal | O(n) | O(n) | Uses prefix sums and hash map to find the shortest subarray efficiently |
Algorithm Walkthrough
- Compute the total sum of
numsmodulopand store it intotal_mod. Iftotal_modis zero, the sum is already divisible byp, so return 0 immediately. - Initialize a hash map
mod_indexto store the last index where each prefix sum modulopoccurs. Prepopulate it with{0: -1}to handle subarrays starting at index 0. - Initialize
prefix_mod = 0andmin_length = n + 1, where n is the length of the array. - Iterate through the array using index
iand valuenum:
a. Update the prefix sum modulo p using prefix_mod = (prefix_mod + num) % p.
b. Calculate the target modulo we need to find in the hash map: target = (prefix_mod - total_mod + p) % p. This accounts for negative differences modulo p.
c. If target exists in mod_index, update min_length with the length of the subarray to remove: i - mod_index[target].
d. Update the hash map with the current prefix modulo: mod_index[prefix_mod] = i.
5. After iteration, if min_length is less than n, return it. Otherwise, return -1 because removing the entire array is not allowed.
Why it works: The algorithm works because we are looking for a subarray whose sum modulo p equals total_mod. By using prefix sums and a hash map, we efficiently track previous prefix sums modulo p and find the shortest valid subarray in a single pass. The invariant is that for any index i, prefix_mod[i] - prefix_mod[j] modulo p gives the sum of the subarray nums[j+1..i] modulo p.
Python Solution
from typing import List
class Solution:
def minSubarray(self, nums: List[int], p: int) -> int:
total_mod = sum(nums) % p
if total_mod == 0:
return 0
mod_index = {0: -1}
prefix_mod = 0
min_length = len(nums) + 1
for i, num in enumerate(nums):
prefix_mod = (prefix_mod + num) % p
target = (prefix_mod - total_mod + p) % p
if target in mod_index:
min_length = min(min_length, i - mod_index[target])
mod_index[prefix_mod] = i
return min_length if min_length < len(nums) else -1
The Python implementation follows the algorithm exactly. We first calculate total_mod and handle the case where no removal is needed. The mod_index dictionary stores the last seen index of each prefix modulo, and we update min_length whenever a valid subarray is found. Finally, we check whether the minimum length is valid before returning.
Go Solution
func minSubarray(nums []int, p int) int {
totalSum := 0
for _, num := range nums {
totalSum += num
}
totalMod := totalSum % p
if totalMod == 0 {
return 0
}
modIndex := map[int]int{0: -1}
prefixMod := 0
minLength := len(nums) + 1
for i, num := range nums {
prefixMod = (prefixMod + num) % p
target := (prefixMod - totalMod + p) % p
if idx, exists := modIndex[target]; exists {
if i - idx < minLength {
minLength = i - idx
}
}
modIndex[prefixMod] = i
}
if minLength < len(nums) {
return minLength
}
return -1
}
The Go implementation mirrors the Python solution. We explicitly handle the index map with integer keys and values. Go does not have built-in negative modulo correction, so we ensure target is positive. Slice indexing and map lookups replace Python dictionary operations.
Worked Examples
Example 1: nums = [3,1,4,2], p = 6
total_mod = (3+1+4+2) % 6 = 10 % 6 = 4
We need to remove a subarray whose sum modulo 6 is 4.
| i | num | prefix_mod | target | mod_index | min_length |
|---|---|---|---|---|---|
| 0 | 3 | 3 | (3-4+6)%6=5 | {0:-1} | 7 |
| 1 | 1 | 4 | (4-4+6)%6=6%6=0 | {0:-1,3:0} | i - (-1) = 2 |
| 2 | 4 | 2 | (2-4+6)%6=4 | {0:-1,3:0,4:1} | 2 |
| 3 | 2 | 4 | (4-4+6)%6=0 | {0:-1,3:0,4:2} | i - (-1)=4 |
Minimum valid length is 1 (removing [4]).
Example 2: nums = [6,3,5,2], p = 9
total_mod = (6+3+5+2)%9 = 16%9 = 7
Target subarray sum modulo = 7. Following the algorithm, the shortest subarray is [5,2] with length 2.
Example 3: nums = [1,2,3], p = 3
total_mod = 6 % 3 = 0
No removal needed, return 0.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass through array, with constant time map operations |
| Space | O(n) | Hash map stores at most n prefix sums modulo p |
The time complexity is linear because we only iterate once over the array. The space complexity is linear due to storing prefix sums modulo p in the hash map.
Test Cases
# Provided examples
assert Solution().minSubarray([3,1,4,2], 6) == 1 # remove [4]
assert Solution().minSubarray([6,3,5,2], 9) == 2 # remove [5,2]
assert Solution().minSubarray([1,2,3], 3) == 0 # already divisible
# Edge cases
assert Solution().minSubarray([1], 2) == -1 # cannot