文章目录

  • 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;

    }
};

结果:

Python中整除运算顺序 python 中整除_leetcode

Python中整除运算顺序 python 中整除_前缀和_02