1590. Make Sum Divisible by P
Problem Description
You are given an array of positive integers nums
and a positive integer p
. Your task is to find and remove the smallest possible subarray (which can be empty) from nums
such that the sum of the remaining elements is divisible by p
.
A subarray is defined as a contiguous sequence of elements in the array. For example, in [1, 2, 3, 4]
, [2, 3]
is a subarray, but [1, 3]
is not.
The key constraints are:
- You cannot remove the entire array (at least one element must remain)
- You want to minimize the length of the removed subarray
- After removal, the sum of remaining elements must be divisible by
p
(i.e.,sum % p == 0
)
Return the length of the smallest subarray that needs to be removed. If it's impossible to achieve the goal, return -1
.
For example:
- If
nums = [3, 1, 4, 2]
andp = 6
, the sum is10
. Since10 % 6 = 4
, we need to remove a subarray with sum4
to make the remaining sum divisible by6
. We can remove[4]
(length 1), making the remaining elements[3, 1, 2]
with sum6
, which is divisible by6
. - If the original sum is already divisible by
p
, return0
(remove an empty subarray). - If no valid subarray can be removed to achieve the goal, return
-1
.
Intuition
Let's think about what we're trying to achieve. We have a total sum of the array, and we want to remove a subarray so that the remaining sum is divisible by p
.
If the total sum is S
and we remove a subarray with sum sub_sum
, then the remaining sum is S - sub_sum
. For this to be divisible by p
, we need:
(S - sub_sum) % p = 0
This can be rewritten as:
S % p = sub_sum % p
So if S % p = k
, we need to find a subarray whose sum modulo p
equals k
. If k = 0
, the array sum is already divisible by p
, so we return 0
.
Now, how do we efficiently find such a subarray? This is where prefix sums come in handy. If we have prefix sums prefix[i]
(sum of first i
elements) and prefix[j]
(sum of first j
elements), then the sum of subarray from index j+1
to i
is prefix[i] - prefix[j]
.
For this subarray sum to have the desired modulo value k
, we need:
(prefix[i] - prefix[j]) % p = k
Rearranging this equation:
prefix[i] % p - prefix[j] % p = k
(with proper handling of negative values)
prefix[j] % p = (prefix[i] % p - k + p) % p
This means, at each position i
, if we know the current prefix sum modulo p
(let's call it cur
), we need to look for a previous position j
where the prefix sum modulo p
was target = (cur - k + p) % p
.
We can use a hash table to store the last occurrence of each prefix sum modulo value. As we traverse the array, for each position, we check if the required target
exists in our hash table. If it does, we've found a valid subarray to remove, and we update our answer with its length. We keep track of the minimum length found.
The beauty of this approach is that we only need one pass through the array, making it efficient with O(n)
time complexity.
Learn more about Prefix Sum patterns.
Solution Approach
Let's walk through the implementation step by step:
Step 1: Calculate the remainder
First, we calculate k = sum(nums) % p
. This tells us what remainder we need to eliminate. If k = 0
, the array sum is already divisible by p
, so we return 0
.
Step 2: Initialize data structures
- We use a hash table
last
to store the most recent index where each prefix sum modulop
value occurred - Initialize
last = {0: -1}
to handle the case where we need to remove a prefix of the array (the-1
index represents the position before the array starts) - Set
cur = 0
to track the current prefix sum modulop
- Set
ans = len(nums)
as the initial answer (worst case)
Step 3: Traverse the array
For each element at index i
with value x
:
-
Update current prefix sum:
cur = (cur + x) % p
This gives us the prefix sum modulop
up to indexi
. -
Calculate target:
target = (cur - k + p) % p
This is the prefix sum modulo value we need to find at some previous positionj
such that removing the subarray fromj+1
toi
gives us the desired remainder. -
Check if target exists: If
target
is inlast
:- We found a valid subarray to remove: from index
last[target] + 1
toi
- Update answer:
ans = min(ans, i - last[target])
- We found a valid subarray to remove: from index
-
Update hash table:
last[cur] = i
Store the current position for this prefix sum modulo value for future lookups.
Step 4: Return result
- If
ans == len(nums)
, it means we couldn't find any valid subarray to remove (the only option would be to remove the entire array, which is not allowed), so return-1
- Otherwise, return
ans
Example walkthrough:
Let's say nums = [3, 1, 4, 2]
and p = 6
:
k = 10 % 6 = 4
(we need to remove a subarray with sum ≡ 4 (mod 6))- As we traverse:
i=0
:cur=3
,target=(3-4+6)%6=5
, not found,last[3]=0
i=1
:cur=4
,target=(4-4+6)%6=0
, found at-1
,ans=min(4, 1-(-1))=2
,last[4]=1
i=2
:cur=2
,target=(2-4+6)%6=4
, found at1
,ans=min(2, 2-1)=1
,last[2]=2
i=3
:cur=4
,target=(4-4+6)%6=0
, found at-1
,ans
stays1
,last[4]=3
- Return
1
(we can remove the subarray[4]
at index 2)
The time complexity is O(n)
and space complexity is O(min(n, p))
for the hash table.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's work through a concrete example to understand the solution approach.
Example: nums = [6, 3, 5, 2]
and p = 9
Step 1: Calculate what we need to remove
- Total sum = 6 + 3 + 5 + 2 = 16
- k = 16 % 9 = 7
- We need to find a subarray whose sum ≡ 7 (mod 9)
Step 2: Initialize
last = {0: -1}
(maps prefix sum mod values to their last seen index)cur = 0
(current prefix sum mod p)ans = 4
(length of array, worst case)
Step 3: Traverse the array
Index 0 (value = 6):
- Update:
cur = (0 + 6) % 9 = 6
- Calculate target:
target = (6 - 7 + 9) % 9 = 8
- Check: 8 not in
last
, so no valid subarray ending here - Update:
last[6] = 0
- State:
last = {0: -1, 6: 0}
Index 1 (value = 3):
- Update:
cur = (6 + 3) % 9 = 0
- Calculate target:
target = (0 - 7 + 9) % 9 = 2
- Check: 2 not in
last
, so no valid subarray ending here - Update:
last[0] = 1
- State:
last = {0: 1, 6: 0}
Index 2 (value = 5):
- Update:
cur = (0 + 5) % 9 = 5
- Calculate target:
target = (5 - 7 + 9) % 9 = 7
- Check: 7 not in
last
, so no valid subarray ending here - Update:
last[5] = 2
- State:
last = {0: 1, 6: 0, 5: 2}
Index 3 (value = 2):
- Update:
cur = (5 + 2) % 9 = 7
- Calculate target:
target = (7 - 7 + 9) % 9 = 0
- Check: 0 is in
last
at index 1!- This means removing subarray from index 2 to 3 works
- Subarray length = 3 - 1 = 2
- Update:
ans = min(4, 2) = 2
- Update:
last[7] = 3
Step 4: Return result
ans = 2
, so we return 2
Verification:
- Removing subarray [5, 2] (indices 2-3) leaves us with [6, 3]
- Sum of remaining = 6 + 3 = 9, which is divisible by 9 ✓
The key insight is that we're using modular arithmetic to efficiently find subarrays with specific sum properties, avoiding the need to check all possible subarrays explicitly.
Solution Implementation
1class Solution:
2 def minSubarray(self, nums: List[int], p: int) -> int:
3 # Calculate the remainder of the total sum divided by p
4 # This is what we need to remove to make the remaining sum divisible by p
5 total_remainder = sum(nums) % p
6
7 # If the total sum is already divisible by p, no subarray needs to be removed
8 if total_remainder == 0:
9 return 0
10
11 # Dictionary to store the last index where each prefix sum modulo p occurred
12 # Initialize with 0: -1 to handle cases where the subarray starts from index 0
13 last_seen_index = {0: -1}
14
15 # Current prefix sum modulo p
16 current_prefix_sum = 0
17
18 # Initialize the answer to the length of the array (worst case)
19 min_length = len(nums)
20
21 # Iterate through each element in the array
22 for index, num in enumerate(nums):
23 # Update the current prefix sum modulo p
24 current_prefix_sum = (current_prefix_sum + num) % p
25
26 # Calculate the target prefix sum we're looking for
27 # We need to find a previous prefix sum such that:
28 # (current_prefix_sum - previous_prefix_sum) % p == total_remainder
29 # This means: previous_prefix_sum == (current_prefix_sum - total_remainder) % p
30 target_prefix_sum = (current_prefix_sum - total_remainder + p) % p
31
32 # If we've seen this target prefix sum before, we can remove the subarray
33 # between that previous index and the current index
34 if target_prefix_sum in last_seen_index:
35 subarray_length = index - last_seen_index[target_prefix_sum]
36 min_length = min(min_length, subarray_length)
37
38 # Update the last seen index for the current prefix sum
39 last_seen_index[current_prefix_sum] = index
40
41 # If min_length equals the array length, we can't remove the entire array
42 # Return -1 in this case, otherwise return the minimum subarray length
43 return -1 if min_length == len(nums) else min_length
44
1class Solution {
2 public int minSubarray(int[] nums, int p) {
3 // Calculate the remainder of the total sum when divided by p
4 int totalRemainder = 0;
5 for (int num : nums) {
6 totalRemainder = (totalRemainder + num) % p;
7 }
8
9 // If the total sum is already divisible by p, no subarray needs to be removed
10 if (totalRemainder == 0) {
11 return 0;
12 }
13
14 // HashMap to store the last index where each prefix sum remainder was seen
15 // Key: prefix sum remainder, Value: index
16 Map<Integer, Integer> lastSeenIndex = new HashMap<>();
17 lastSeenIndex.put(0, -1); // Initialize with remainder 0 at index -1
18
19 int n = nums.length;
20 int minLength = n; // Initialize minimum subarray length to remove as n (impossible case)
21 int currentPrefixRemainder = 0;
22
23 // Iterate through the array to find the minimum subarray to remove
24 for (int i = 0; i < n; i++) {
25 // Calculate current prefix sum remainder
26 currentPrefixRemainder = (currentPrefixRemainder + nums[i]) % p;
27
28 // Calculate the target remainder we need to find
29 // We need: (currentPrefixRemainder - targetRemainder) % p == totalRemainder
30 // Therefore: targetRemainder = (currentPrefixRemainder - totalRemainder + p) % p
31 int targetRemainder = (currentPrefixRemainder - totalRemainder + p) % p;
32
33 // If we've seen this target remainder before, we can potentially remove
34 // the subarray between that previous index and current index
35 if (lastSeenIndex.containsKey(targetRemainder)) {
36 int subarrayLength = i - lastSeenIndex.get(targetRemainder);
37 minLength = Math.min(minLength, subarrayLength);
38 }
39
40 // Update the last seen index for current prefix remainder
41 lastSeenIndex.put(currentPrefixRemainder, i);
42 }
43
44 // If minLength is still n, no valid subarray was found
45 // Otherwise, return the minimum length
46 return minLength == n ? -1 : minLength;
47 }
48}
49
1class Solution {
2public:
3 int minSubarray(vector<int>& nums, int p) {
4 // Calculate the remainder of the total sum divided by p
5 int totalRemainder = 0;
6 for (int& num : nums) {
7 totalRemainder = (totalRemainder + num) % p;
8 }
9
10 // If the total sum is already divisible by p, no need to remove any subarray
11 if (totalRemainder == 0) {
12 return 0;
13 }
14
15 // Hash map to store the last occurrence index of each prefix sum remainder
16 // Key: prefix sum remainder, Value: index
17 unordered_map<int, int> lastIndexMap;
18 lastIndexMap[0] = -1; // Initialize with remainder 0 at index -1 (before array starts)
19
20 int n = nums.size();
21 int minLength = n; // Initialize with array size (worst case: remove entire array)
22 int currentPrefixRemainder = 0;
23
24 // Iterate through the array to find the minimum length subarray to remove
25 for (int i = 0; i < n; ++i) {
26 // Update current prefix sum remainder
27 currentPrefixRemainder = (currentPrefixRemainder + nums[i]) % p;
28
29 // Calculate the target remainder we need to find
30 // We need: (currentPrefixRemainder - targetRemainder) % p == totalRemainder
31 // So: targetRemainder = (currentPrefixRemainder - totalRemainder) % p
32 int targetRemainder = (currentPrefixRemainder - totalRemainder + p) % p;
33
34 // Check if we've seen this target remainder before
35 if (lastIndexMap.count(targetRemainder)) {
36 // Update minimum length if we found a shorter subarray
37 minLength = min(minLength, i - lastIndexMap[targetRemainder]);
38 }
39
40 // Store the current prefix remainder with its index
41 lastIndexMap[currentPrefixRemainder] = i;
42 }
43
44 // If minLength is still n, we cannot remove any valid subarray
45 // (cannot remove the entire array), return -1
46 return minLength == n ? -1 : minLength;
47 }
48};
49
1/**
2 * Finds the minimum length of a subarray that can be removed so that
3 * the sum of remaining elements is divisible by p
4 * @param nums - The input array of numbers
5 * @param p - The divisor
6 * @returns The minimum length of subarray to remove, or -1 if impossible
7 */
8function minSubarray(nums: number[], p: number): number {
9 // Calculate the remainder when total sum is divided by p
10 let totalRemainder: number = 0;
11 for (const num of nums) {
12 totalRemainder = (totalRemainder + num) % p;
13 }
14
15 // If the total sum is already divisible by p, no removal needed
16 if (totalRemainder === 0) {
17 return 0;
18 }
19
20 // Map to store the last index where each prefix sum modulo p occurred
21 const prefixSumLastIndex: Map<number, number> = new Map<number, number>();
22 prefixSumLastIndex.set(0, -1); // Initialize with prefix sum 0 at index -1
23
24 const arrayLength: number = nums.length;
25 let minLength: number = arrayLength; // Initialize with maximum possible length
26 let currentPrefixSum: number = 0;
27
28 // Iterate through the array to find the minimum subarray
29 for (let i = 0; i < arrayLength; ++i) {
30 // Update current prefix sum modulo p
31 currentPrefixSum = (currentPrefixSum + nums[i]) % p;
32
33 // Calculate the target prefix sum we need to find
34 // We need (currentPrefixSum - targetPrefixSum) % p = totalRemainder
35 const targetPrefixSum: number = (currentPrefixSum - totalRemainder + p) % p;
36
37 // Check if we've seen this target prefix sum before
38 if (prefixSumLastIndex.has(targetPrefixSum)) {
39 const previousIndex: number = prefixSumLastIndex.get(targetPrefixSum)!;
40 // Update minimum length if we found a shorter subarray
41 minLength = Math.min(minLength, i - previousIndex);
42 }
43
44 // Store the current prefix sum with its index
45 prefixSumLastIndex.set(currentPrefixSum, i);
46 }
47
48 // Return -1 if we need to remove the entire array (invalid), otherwise return the minimum length
49 return minLength === arrayLength ? -1 : minLength;
50}
51
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the array nums
exactly once using a single for loop with enumerate(nums)
. Within each iteration, all operations are constant time:
- Computing
(cur + x) % p
isO(1)
- Computing
target = (cur - k + p) % p
isO(1)
- Dictionary lookup
target in last
isO(1)
on average - Dictionary insertion
last[cur] = i
isO(1)
on average - The
min()
comparison isO(1)
Additionally, the initial sum(nums)
operation takes O(n)
time. Therefore, the overall time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The algorithm uses a dictionary last
to store the mapping between prefix sum remainders and their indices. In the worst case, all prefix sums modulo p
are distinct, meaning the dictionary could store up to n + 1
entries (including the initial entry {0: -1}
). All other variables (k
, cur
, ans
, target
) use constant space. Therefore, the space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Sum Calculation
Pitfall: When calculating sum(nums) % p
, the sum might overflow for very large arrays with large values, even though Python handles big integers automatically. In other languages like Java or C++, this is a critical issue.
Solution: Calculate the modulo during the summation process:
total_remainder = 0 for num in nums: total_remainder = (total_remainder + num) % p
2. Incorrect Modulo Arithmetic for Negative Values
Pitfall: The expression (current_prefix_sum - total_remainder) % p
can be negative, and in some programming languages, negative modulo operations don't behave as expected. Even in Python, forgetting to add p
before taking modulo can lead to issues in edge cases.
Wrong approach:
target_prefix_sum = (current_prefix_sum - total_remainder) % p # Can be negative!
Correct approach:
target_prefix_sum = (current_prefix_sum - total_remainder + p) % p # Always non-negative
3. Forgetting to Initialize the HashMap with {0: -1}
Pitfall: Not initializing last_seen_index = {0: -1}
means you can't handle cases where the subarray to remove starts from index 0 (i.e., removing a prefix of the array).
Example where this matters:
nums = [1, 2, 3]
,p = 3
- Total sum = 6, which is divisible by 3
- But if we need to remove a prefix like
[1, 2]
, we need the initial{0: -1}
entry
4. Updating HashMap Before Checking
Pitfall: Updating last_seen_index[current_prefix_sum] = index
before checking for the target can cause incorrect results when the current position itself could be part of the answer.
Wrong order:
for index, num in enumerate(nums):
current_prefix_sum = (current_prefix_sum + num) % p
last_seen_index[current_prefix_sum] = index # Updated too early!
target_prefix_sum = (current_prefix_sum - total_remainder + p) % p
if target_prefix_sum in last_seen_index:
# This might use the current index incorrectly
min_length = min(min_length, index - last_seen_index[target_prefix_sum])
Correct order: Always check first, then update.
5. Not Handling the Case Where Only Full Array Removal Works
Pitfall: Forgetting to check if min_length == len(nums)
at the end. If the only way to make the sum divisible by p
is to remove all elements, we must return -1
since removing the entire array is not allowed.
Missing check:
return min_length # Wrong! Could return len(nums)
Correct check:
return -1 if min_length == len(nums) else min_length
Which of the following uses divide and conquer strategy?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!