给你一个正整数数组 nums
,请你移除 最短 子数组(可以为 空),使得剩余元素的 和 能被 p
整除。 不允许 将整个数组都移除。
请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1
。
子数组 定义为原数组中连续的一组元素。
示例 1:
输入:nums = [3,1,4,2], p = 6 输出:1 解释:nums 中元素和为 10,不能被 p 整除。我们可以移除子数组 [4] ,剩余元素的和为 6 。
示例 2:
输入:nums = [6,3,5,2], p = 9 输出:2 解释:我们无法移除任何一个元素使得和被 9 整除,最优方案是移除子数组 [5,2] ,剩余元素为 [6,3],和为 9 。
示例 3:
输入:nums = [1,2,3], p = 3 输出:0 解释:和恰好为 6 ,已经能被 3 整除了。所以我们不需要移除任何元素。
示例 4:
输入:nums = [1,2,3], p = 7 输出:-1 解释:没有任何方案使得移除子数组后剩余元素的和被 7 整除。
示例 5:
输入:nums = [1000000000,1000000000,1000000000], p = 3 输出:0
方案
- 首先,我们要知道一个数学性质:如果两个数的差能被 p 整除,那么它们对 p 取模的结果也相等。比如 10 - 4 = 6 能被 3 整除,那么 10 % 3 = 1 和 4 % 3 = 1 也相等。
- 其次,我们要知道什么是前缀和。前缀和就是从数组开头到某个位置的所有元素的和。比如数组 [3,1,4,2] 的前缀和分别是 [3,4,8,10] 。前缀和可以帮助我们快速计算某个区间的元素之和,比如从第二个元素到第四个元素的和就是前缀和[4] - 前缀和[1] = 10 - 3 = 7。
- 然后,我们要知道为什么要用哈希表。哈希表可以帮助我们快速查找某个值是否存在以及它出现在哪里。比如在上面的例子中,如果我们想知道是否有一个前缀和等于7,我们可以用哈希表存储每个前缀和及其位置,然后查找是否有键为7的项。如果有,则说明存在这样一个前缀和,并且可以得到它出现在第四个位置。
- 最后,我们要知道怎么利用这些知识来解决问题。我们先计算数组 nums 的总和 sum ,并对 p 取模得到余数 mod 。这个余数就是我们需要移除掉的子数组的和对 p 取模的结果。如果 mod 等于0,说明不需要移除任何元素,直接返回0即可。
- 如果 mod 不等于0,说明需要移除一个子数组使得剩余元素的和能被 p 整除。这时候就要用到前面提到的数学性质:如果两个前缀和对 p 取模相等,则它们之间的子数组之和能被 p 整除。所以我们只需要找到两个位置 i 和 j ,使得 (cur[i] % p) == (cur[j] % p) == mod ,那么从 j + 1 到 i 的子数组就是一个候选答案。
- 为了快速找到这样的两个位置 i 和 j ,我们可以用一个哈希表 prefixMap 来记录每个前缀和对 p 取模及其位置。然后遍历数组 nums ,计算当前位置 i 的前缀和 cur ,并对 p 取模得到 curMod 。如果 curMod 等于 mod ,说明从开头到当前位置 i 的子数组符合要求;否则,在哈希表中查找是否存在一个键为 (curMod - mod + p) % p 的项(这里加上p是为了防止负数),如果存在,则说明从 j + 1 到 i 的子数组也符合要求。
- 每次找到一个候选答案后,我们更新最短长度为当前长度与之前最短长度中较小者。最后返回最短长度即可。
Go代码
func minSubarray(nums []int, p int) int {
// 计算总和并取模
sum := 0
for _, num := range nums {
sum += num
}
mod := sum % p
// 如果余数为0,则不需要移除任何元素
if mod == 0 {
return 0
}
// 哈希表记录前缀和取模及其位置
prefixMap := make(map[int]int)
prefixMap[0] = -1 // 初始化
// 遍历数组寻找最短子数组
cur := 0 // 当前前缀和
ans := len(nums) // 最短长度
for i, num := range nums {
cur += num // 更新前缀和
curMod := cur % p // 前缀和取模
if curMod == mod {
// 如果当前前缀和取模等于目标余数,则从开头到当前位置的子数组符合要求
ans = min(ans, i + 1)
}
targetMod := (curMod - mod + p) % p
// 目标余数:如果存在一个位置j使得从j+1到i的子数组符合要求,则必有(cur[j] % p) == targetMod
if j, ok := prefixMap[targetMod]; ok {
// 在哈希表中查找是否有符合要求的位置j
ans = min(ans, i - j)
}
prefixMap[curMod] = i // 将当前前缀和取模及其位置存入哈希表
}
if ans == len(nums) {
// 如果没有找到任何符合要求的子数组,则返回-1
return -1
}
return ans
}
// 辅助函数:求两个数中较小者。
func min(a, b int) int {
if a < b {
return a
}
return b
}