LeetCode 1590. Make Sum Divisible by P
题目描述
给你一个正整数数组 nums,请你移除最短子数组(可以为空),使得剩余元素的和能被 p 整除。不允许将整个数组都移除。返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1。
例如:nums = [3,1,4,2],p = 6,输出 1。
解释:数组总和为 3+1+4+2=10,10 % 6 = 4。我们需要移除一个和为 4 的子数组。移除子数组 [4](索引 2),剩余元素和 3+1+2=6,能被 6 整除。子数组长度为 1,是最短的。
解题思路
核心数学原理
设数组总和模 p 的值为 totalSum % p = target:
- 如果
target == 0,说明整个数组的和已经能被p整除,直接返回0。 - 否则,我们需要移除一个子数组,使得该子数组的和模
p等于target,这样剩余元素的和模p就等于0。
为什么? 因为剩余和 = 总和 - 子数组和。若剩余和能被 p 整除,即 (totalSum - subSum) % p == 0,则 subSum % p == totalSum % p == target。
前缀和 + 哈希表
问题转化为:寻找最短的连续子数组,其和模 p 等于 target。
设 prefix[i] 为 nums[0..i] 的前缀和模 p。如果存在 j < i 使得 (prefix[i] - prefix[j]) % p == target,则子数组 nums[j+1..i] 满足条件。
即我们需要 prefix[j] == (prefix[i] - target) % p(注意处理负数取模)。
算法步骤:
- 计算
totalSum % p得到target。如果为 0,返回 0。 - 使用哈希表
map记录每个前缀和模值最后一次出现的位置(或第一次出现的位置),初始化map.put(0, -1)。 - 遍历数组,维护当前前缀和模值
curSum:- 计算需要的值
needed = (curSum - target + p) % p。 - 如果
map中存在needed,说明找到了符合条件的子数组[map.get(needed)+1, i],更新最小长度。 - 将
(curSum, i)存入map(记录当前位置,确保能找到最短子数组)。
- 计算需要的值
- 返回最小长度,若为 n 则返回 -1。
示例说明
以 nums = [3,1,4,2],p = 6 为例:
- totalSum = 10,target = 4
- map = {0: -1},curSum = 0
- i=0(3):curSum = 3,needed = (3-4+6)%6 = 5,不在 map;map = {0:-1, 3:0}
- i=1(1):curSum = 4,needed = (4-4+6)%6 = 0,在 map(位置 -1)→ 长度 = 1-(-1) = 2;map = {0:-1, 3:0, 4:1}
- i=2(4):curSum = 2,needed = (2-4+6)%6 = 4,在 map(位置 1)→ 长度 = 2-1 = 1,更新 minLen = 1;map = {…, 2:2}
- i=3(2):curSum = 4,needed = 0,在 map(位置 -1)→ 长度 = 3-(-1) = 4;minLen = 1
- 返回 1
复杂度分析
- 时间复杂度:
O(n),一次遍历完成。 - 空间复杂度:
O(n),哈希表最多存储 n 个前缀和模值。
代码
1 | package code.J1590; |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 𝒞𝒶𝓃𝒶𝓇𝓎!