LeetCode 每日一题笔记
0. 前言
- 日期:2025.11.30
- 题目:1590.使数组和能被 P 整除
- 难度:中等
- 标签:数组 前缀和 哈希表
1. 题目理解
问题描述 :给你一个正整数数组 nums,请你移除最短子数组 (可以为空),使得剩余元素的和能被 p 整除。不允许将整个数组都移除。请返回需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1。
示例:
示例 1:
输入:nums = [3,1,4,2], p = 6
输出:1
解释:数组和为 10,不能被 6 整除。移除子数组 [4] 后,剩余和为 6,可被 6 整除。
示例 2:输入:nums = [6,3,5,2], p = 9
输出:2
解释:移除子数组 [5,2] 后,剩余和为 9,可被 9 整除。
2. 解题思路
核心观察
- 设数组总和对
p的余数为x,若x=0,则无需移除子数组; - 需移除的子数组和对
p的余数必须等于x(这样剩余和对p的余数为0); - 利用前缀和取模 +哈希表快速找到满足余数条件的最短子数组。
算法步骤
- 计算数组总和对
p的余数x,若x=0直接返回0; - 维护一个哈希表,记录前缀和余数 对应的最早出现的下标;
- 遍历数组,计算当前前缀和余数
y,并计算目标余数(y - x + p) % p(避免负数); - 若哈希表中存在目标余数,计算当前子数组长度并更新最小值;
- 遍历结束后,若最短长度等于数组长度,返回
-1,否则返回最短长度。
3. 代码实现(初始版本)
java
class Solution {
public int minSubarray(int[] nums, int p) {
// 初始化前缀和数组与memory数组
int []prefix =new int[nums.length];
int []memory =new int[p];
// 计算前缀和数组(注:初始版本未对前缀和取模,存在逻辑错误)
prefix[0]=nums[0]%p;
for(int i=1;i<nums.length;i++){
int a=nums[i]%p;
prefix[i]=a+prefix[i-1]; // 错误:未对累加结果取模,会导致余数错误
}
// 计算数组总和对p的余数
int end =prefix[nums.length-1]%p;
if(end == 0){return 0;}
// 逻辑处理(注:初始版本嵌套循环效率低且逻辑混乱)
for(int i=1;i<nums.length;i++){
for(int j=0;j<i;j++){
int b=(prefix[i]-prefix[j])%p;
if(memory[b]>j||memory[b]==0){memory[b]=j+1;} // 错误:子数组长度计算逻辑错误
}
}
// 结果输出(注:初始版本判断条件错误)
if(memory[end]==0){return -1;}
return memory[end];
}
}
4. 代码优化说明(官方题解版本)
java
class Solution {
public int minSubarray(int[] nums, int p) {
// 计算数组总和对p的余数x
int x = 0;
for (int num : nums) {
x = (x + num) % p; // 累加过程中取模,避免溢出
}
// 总和已能被p整除,直接返回0
if (x == 0) {
return 0;
}
// 哈希表:记录前缀和余数对应的最早下标
Map<Integer, Integer> index = new HashMap<Integer, Integer>();
int y = 0; // 当前前缀和余数
int res = nums.length; // 初始化最短长度为数组长度
for (int i = 0; i < nums.length; i++) {
index.put(y, i); // 记录当前前缀和余数对应的下标
y = (y + nums[i]) % p; // 更新前缀和余数
// 计算目标余数(避免负数)
int target = (y - x + p) % p;
// 找到目标余数,更新最短长度
if (index.containsKey(target)) {
res = Math.min(res, i - index.get(target) + 1);
}
}
// 无法满足条件则返回-1,否则返回最短长度
return res == nums.length ? -1 : res;
}
}
5. 复杂度分析
- 时间复杂度 :O(n)O(n)O(n),其中 nnn 是数组长度。仅需遍历数组一次,哈希表操作时间为 O(1)O(1)O(1);
- 空间复杂度 :O(p)O(p)O(p),哈希表最多存储 ppp 个不同的余数(余数范围为 [0,p−1][0, p-1][0,p−1])。
6. 总结
- 核心思路是利用前缀和取模 将问题转化为"余数匹配",再通过哈希表快速定位满足条件的子数组;
- 初始版本的问题在于前缀和未取模、子数组长度计算逻辑混乱、嵌套循环效率低;
- 优化后的官方题解通过哈希表记录余数下标,将时间复杂度从 O(n2)O(n^2)O(n2) 降至 O(n)O(n)O(n),是该问题的最优解法之一。