文章目录
- leetcode1590. 使数组和能被 P 整除
- 方法:前缀和+哈希表
- 思路:
- 代码:
- Python3:
- cpp:
- 结果:
leetcode1590. 使数组和能被 P 整除
给你一个正整数数组 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
提示:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^9
1 <= p <= 10^9
方法:前缀和+哈希表
思路:
本题考虑的是数组的和能否被p整除,且移除的是一小段连续数组,可以想到与前缀和有关。
考虑到整除与否只与余数有关,同时,为了找到移除的最短数组,可以考虑使用哈希表来保存余数出现的最近位置来更新答案。
我们首先计算整个数组的前缀和presum,
- 如果
presum%p==0
,那么直接返回0即可,不需要移除数组。 - 如果
presum<p
,那么说明无论怎么移除,除非移除全部数组(不可行的),都不可能整除,因此直接返回-1。
对于其他情况,我们就要考虑删除数组,我们设置mod = presum % p,然后重置presum=0,然后遍历数组开始更新presum,这次,presum不保存前缀和,保存的是前缀和对p的余数。
那么我们知道,**如果某个子数组的和对p的余数也是mod,那么移除它之后即可完成任务,剩余数组可以整除p。**假设遍历过程中,前缀和对p的余数为presum,那么:
- 如果
presum >= mod
,那么如果前面出现过presum-mod
这个余数,那么从那个位置,到现在这个位置,这一段子数组的余数即为mod。 - 如果
presum < mod
,那么如果前面出现过presum-mod+p
这个余数,那么从那个位置,到现在这个位置,这一段子数组的余数即为mod。 - 上面两种情况可以通用使用,找前面出现
(presum-mod+p)%p
这个余数的位置即可。
我们使用一个哈希表来保存某个前缀和余数最近出现的位置,hashmap[0]=-1,这个是为了考虑从头开始的情况,计算长度时候要+1。
假设target = (presum-mod+p)%p,那么我们这段子数组长度即为i-hashmap[target]
。不断更新,返回最小的长度,最后判断,如果res大于等于数组长度,返回-1。
代码:
Python3:
class Solution:
def minSubarray(self, nums: List[int], p: int) -> int:
# 先求前缀和
presum = 0
for num in nums:
presum += num
# 如果整个数组和满足整除,不需要删除,直接返回0
if presum % p == 0:
return 0
# 如果前缀和小于p,那么不存在答案,返回-1
if presum < p:
return -1
# mod为前缀和的余数,我们需要找到一个最短子数组,使得它的和的余数与mod一致
mod = presum % p
# 使用字典来保存距离最近的前缀和对p的余数出现的位置
# 键为前缀和对p的余数,值为最近出现的下标
# hashmap[0] = -1,是为了计算后面第一次出现余数为0的时候,从头开始的长度
hashmap = dict()
hashmap[0] = -1
# res答案初始为正无穷
res = float('inf')
# 重置前缀和为0
presum = 0
for i in range(len(nums)):
# presum保存当前前缀和对p的余数
presum = (nums[i] + presum)%p
# target为找的目标,如果这个目标之前出现过,那么这段长度是一个符合条件的移除子数组
target = (presum - mod + p) % p
if target in hashmap:
res = min(res,i-hashmap[target])
# 更新余数的出现位置为最新
hashmap[presum] = i
# 返回res,因为不允许移除整个数组,需要判断一下
return res if res < len(nums) else -1
cpp:
class Solution {
public:
int minSubarray(vector<int>& nums, int p) {
// 先求前缀和
long long presum = 0;
for (auto num:nums)
presum += num;
// 如果整个数组和满足整除,不需要删除,直接返回0
if (presum % p == 0)
return 0;
// 如果前缀和小于p,那么不存在答案,返回-1
if (presum < p)
return -1;
// mod为前缀和的余数,我们需要找到一个最短子数组,使得它的和的余数与mod一致
int mod = presum % p;
// 使用字典来保存距离最近的前缀和对p的余数出现的位置
// 键为前缀和对p的余数,值为最近出现的下标
// hashmap[0] = -1,是为了计算后面第一次出现余数为0的时候,从头开始的长度
unordered_map<int,int>hashmap;
hashmap[0] = -1;
// res答案初始为正无穷
int res = INT_MAX;
// 重置前缀和为0
presum = 0;
int target;
for(int i = 0; i < nums.size(); i++){
// presum保存当前前缀和对p的余数
presum = (nums[i] + presum)%p;
// target为找的目标,如果这个目标之前出现过,那么这段长度是一个符合条件的移除子数组
target = (presum - mod + p) % p;
if (hashmap.find(target) != hashmap.end())
res = min(res,i-hashmap[target]);
// 更新余数的出现位置为最新
hashmap[presum] = i;
}
// 返回res,因为不允许移除整个数组,需要判断一下
return res >= nums.size() ? -1:res;
}
};
结果: