leetcode
leetcode 2651 ~ 2700
使数组和能被 P 整除

使数组和能被 P 整除

难度:

标签:

题目描述

Given an array of positive integers nums, remove the smallest subarray (possibly empty) such that the sum of the remaining elements is divisible by p. It is not allowed to remove the whole array.

Return the length of the smallest subarray that you need to remove, or -1 if it's impossible.

A subarray is defined as a contiguous block of elements in the array.

 

Example 1:

Input: nums = [3,1,4,2], p = 6
Output: 1
Explanation: The sum of the elements in nums is 10, which is not divisible by 6. We can remove the subarray [4], and the sum of the remaining elements is 6, which is divisible by 6.

Example 2:

Input: nums = [6,3,5,2], p = 9
Output: 2
Explanation: We cannot remove a single element to get a sum divisible by 9. The best way is to remove the subarray [5,2], leaving us with [6,3] with sum 9.

Example 3:

Input: nums = [1,2,3], p = 3
Output: 0
Explanation: Here the sum is 6. which is already divisible by 3. Thus we do not need to remove anything.

 

Constraints:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 1 <= p <= 109

代码结果

运行时间: 91 ms, 内存: 35.5 MB


/*
 * 思路:
 * 使用Java Stream API来计算数组总和,并利用传统循环和HashMap来处理前缀和的模运算。
 */
import java.util.HashMap;
import java.util.stream.IntStream;

public class SolutionStream {
    public int minSubarray(int[] nums, int p) {
        int totalSum = IntStream.of(nums).sum();
        int target = totalSum % p;
        if (target == 0) return 0;
        HashMap<Integer, Integer> prefixMod = new HashMap<>();
        prefixMod.put(0, -1);
        int prefixSum = 0, minLen = nums.length;
        for (int i = 0; i < nums.length; i++) {
            prefixSum += nums[i];
            int currentMod = prefixSum % p;
            int requiredMod = (currentMod - target + p) % p;
            if (prefixMod.containsKey(requiredMod)) {
                minLen = Math.min(minLen, i - prefixMod.get(requiredMod));
            }
            prefixMod.put(currentMod, i);
        }
        return minLen == nums.length ? -1 : minLen;
    }
}

解释

方法:

题解采用了前缀和与哈希表来解决问题。首先,计算数组总和的余数 k(与 p 的余数),如果 k 为 0,则整个数组和已经是 p 的倍数,直接返回 0。如果 k 不为 0,需要找到一个最短的子数组,移除后剩余部分的和为 p 的倍数。通过逐步构建前缀和,并计算每一步的余数 y 存储到哈希表中(键为余数,值为对应的索引)。为了找到符合条件的子数组,计算每个位置的前缀和余数 x,它是将当前余数调整到我们需要的余数。如果这个 x 出现在哈希表中,则说明从该位置到当前位置的子数组是一个候选子数组。通过比较和更新最小长度,最终得到结果。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在计算前缀和的余数后,需要再计算一个新的余数 x?这个步骤的逻辑基础是什么?
在求解该问题时,我们的目标是通过移除数组中的某个子数组来使得剩余部分的和能被 p 整除。计算出前缀和的余数 y 后,我们需要进一步找到一个子数组,使得移除它之后的数组和的余数为 0。通过计算新的余数 x = (y - k + p) % p,我们实际上是在查找一个之前的前缀和的余数,使得两者的差恰好等于数组总和的余数 k,这样移除两者之间的部分后,余数就从 y 变为 0,达到了题目的要求。这是因为如果两个前缀和的余数之差等于 k,那么这两个前缀和之间的部分(即子数组)的和必定是 p 的倍数。
🦆
在哈希表中,为什么要将初始余数0的位置设为-1?这样做有什么特别的意义或好处?
在哈希表中设置初始余数 0 的位置为 -1 是为了处理从数组开始到当前位置的子数组可以直接使得和为 p 的倍数的情况。例如,如果数组的前几个元素之和就已经是 p 的倍数,那么这时候的前缀和的余数为 0。如果不设置余数 0 的初始位置为 -1,我们就无法正确地计算出从数组开始到当前位置的子数组长度,因为通常哈希表记录的是前缀和余数第一次出现的位置。设置为 -1 可以让我们通过计算 i - (-1) = i + 1 来得到正确的子数组长度。
🦆
题解中提到通过比较和更新最小长度,能否详细解释如何通过哈希表来确定最短的可移除子数组?
哈希表在这个算法中用来存储某个余数第一次出现的索引位置。当我们在遍历数组并计算当前元素的前缀和的余数 y 时,我们计算出调整后的余数 x。如果这个 x 已经在哈希表中存在,这意味着从哈希表中 x 对应的索引位置+1到当前索引 i 的子数组的和是 p 的倍数。这是因为这两个位置的前缀和的余数相同或者符合我们调整后的余数关系,从而使得这部分子数组和的余数为 0。通过记录并更新这些子数组的长度,我们可以找到最短的符合条件的子数组。每次找到符合条件的子数组时,我们比较并更新记录的最短长度,直到遍历完整个数组。

相关问题