Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1590. Make Sum Divisible by P - Leetcode Solution

Problem Description

Given an array of positive integers nums and a positive integer p, your task is to remove the smallest possible non-empty subarray (i.e., a contiguous sequence of elements) from nums such that the sum of the remaining elements is divisible by p. You cannot remove the entire array. If it is not possible to make the sum divisible by p by removing any subarray, return -1.

  • Each element in nums is a positive integer.
  • You must remove a contiguous subarray (at least one element, but not the whole array).
  • The goal is to minimize the length of the subarray you remove.
  • Return -1 if it's impossible.

Thought Process

At first glance, this problem might seem to require checking all possible contiguous subarrays and testing if removing them makes the sum of the rest divisible by p. However, this brute-force approach would be far too slow for large arrays.

The key insight is to reframe the problem: Instead of focusing on what to remove, focus on what remains. If removing a subarray makes the sum of the rest divisible by p, then the sum of the removed subarray must have a remainder equal to the total sum modulo p. This observation allows us to use prefix sums and hash maps to efficiently find the shortest subarray whose sum has the required remainder.

We move from a brute-force, nested loop approach to a more optimal algorithm based on prefix sums and modular arithmetic.

Solution Approach

Let's break down the optimized algorithm step-by-step:

  1. Calculate the total sum:
    • Compute total = sum(nums).
    • If total % p == 0, then the sum is already divisible by p, so we don't need to remove anything. But since removing nothing is not allowed, we return 0 only if a non-empty subarray can be removed; otherwise, proceed.
  2. Find the target remainder:
    • Let rem = total % p. We need to remove a subarray whose sum modulo p is exactly rem.
  3. Use prefix sums and a hash map:
    • As we iterate, keep track of the prefix sum modulo p at each index.
    • For each index, check if there is a previous prefix sum such that the difference between the current prefix sum and that previous prefix sum modulo p equals rem.
    • To do this efficiently, use a hash map to store the most recent index for each prefix sum modulo p.
  4. Track the minimum subarray length:
    • For each valid subarray found, update the minimum length.
    • At the end, if a valid subarray was found and its length is less than the array length, return its length; otherwise, return -1.

We use a hash map for constant time lookups of prefix sum remainders, which is crucial for efficiency.

Example Walkthrough

Consider nums = [3, 1, 4, 2] and p = 6.

  1. Total sum: 3 + 1 + 4 + 2 = 10
  2. Total sum modulo p: 10 % 6 = 4. So, rem = 4.
  3. Goal: Remove the shortest subarray whose sum modulo 6 is 4.
  4. Prefix sums modulo 6:
    • At index 0: 3 % 6 = 3
    • At index 1: (3 + 1) % 6 = 4
    • At index 2: (3 + 1 + 4) % 6 = 8 % 6 = 2
    • At index 3: (3 + 1 + 4 + 2) % 6 = 10 % 6 = 4
  5. Hash map tracking:
    • Initialize with {0: -1} (for an empty prefix).
    • At each index, check if there is a previous prefix sum such that (current_mod - rem) % p exists in the map.
  6. Find subarrays:
    • At index 1 (prefix sum 4): (4 - 4) % 6 = 0. 0 is in the map at index -1. So, removing subarray nums[0:2] (i.e., [3,1]) gives sum 4, which is the required remainder.
    • Length is 2. Continue to check for shorter subarrays.
    • At index 3 (prefix sum 4): (4 - 4) % 6 = 0. 0 is in the map at index -1. Subarray nums[0:4] (whole array) is not allowed.
  7. Answer: The shortest subarray to remove is [3,1] (length 2).

Time and Space Complexity

  • Brute-force approach: For each possible subarray, calculate the sum and check if removing it makes the total sum divisible by p. This takes O(N2) time and O(1) space (not counting input).
  • Optimized approach: We iterate through the array once, maintaining a hash map of prefix sums modulo p. Each lookup and update is O(1), so the overall time complexity is O(N). The space complexity is O(p), since the hash map stores at most p different remainders.

Summary

By reframing the problem in terms of modular arithmetic and prefix sums, we avoid brute-force enumeration and achieve significant speedup. The main insight is that removing a subarray with a specific sum remainder aligns with the desired divisibility of the total sum. Using a hash map for prefix sum remainders allows us to efficiently find the shortest subarray to remove, resulting in an elegant and optimal solution.

Code Implementation

class Solution:
    def minSubarray(self, nums, p):
        total = sum(nums)
        rem = total % p
        if rem == 0:
            return 0
        prefix = 0
        res = len(nums)
        mods = {0: -1}
        for i, num in enumerate(nums):
            prefix = (prefix + num) % p
            target = (prefix - rem) % p
            if target in mods:
                res = min(res, i - mods[target])
            mods[prefix] = i
        return res if res < len(nums) else -1
    
class Solution {
public:
    int minSubarray(vector<int>& nums, int p) {
        int n = nums.size();
        long total = 0;
        for (int num : nums) total += num;
        int rem = total % p;
        if (rem == 0) return 0;
        unordered_map<int, int> mods;
        mods[0] = -1;
        int res = n;
        long prefix = 0;
        for (int i = 0; i < n; ++i) {
            prefix = (prefix + nums[i]) % p;
            int target = (prefix - rem + p) % p;
            if (mods.count(target)) {
                res = min(res, i - mods[target]);
            }
            mods[prefix] = i;
        }
        return res < n ? res : -1;
    }
};
    
class Solution {
    public int minSubarray(int[] nums, int p) {
        long total = 0;
        for (int num : nums) total += num;
        int rem = (int)(total % p);
        if (rem == 0) return 0;
        Map<Integer, Integer> mods = new HashMap<>();
        mods.put(0, -1);
        int res = nums.length;
        long prefix = 0;
        for (int i = 0; i < nums.length; ++i) {
            prefix = (prefix + nums[i]) % p;
            int target = (int)((prefix - rem + p) % p);
            if (mods.containsKey(target)) {
                res = Math.min(res, i - mods.get(target));
            }
            mods.put((int)prefix, i);
        }
        return res < nums.length ? res : -1;
    }
}
    
var minSubarray = function(nums, p) {
    let total = nums.reduce((a, b) => a + b, 0);
    let rem = total % p;
    if (rem === 0) return 0;
    let mods = new Map();
    mods.set(0, -1);
    let res = nums.length;
    let prefix = 0;
    for (let i = 0; i < nums.length; ++i) {
        prefix = (prefix + nums[i]) % p;
        let target = (prefix - rem + p) % p;
        if (mods.has(target)) {
            res = Math.min(res, i - mods.get(target));
        }
        mods.set(prefix, i);
    }
    return res < nums.length ? res : -1;
};