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

方案

  • 首先,我们要知道一个数学性质:如果两个数的差能被 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 
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值