Qwen3.5-Plus LintCode 3880.连续子数组求和(四) public boolean checkSubarraySum(int[] nums, int k, int n)
根据搜索结果,LintCode 3880. 连续子数组求和(四) 的问题描述如下:
题目描述
给定一个非负整数数组 nums、两个整数 k 和 n,验证 nums 是否存在一个好的子数组。
好子数组的定义:
子数组是连续的。
子数组长度至少为 2。
子数组元素总和为 k 的倍数加 n,即 sum(subarray) % k == n。
注意:0 可以看成是 k 的一个倍数(通常指当和为0时,若 n=0 则满足条件)。
如果存在这样的子数组,返回 true,否则返回 false。
示例:
输入:nums = [23, 2, 4, 6, 7], k = 6, n = 4
输出:true
解释:子数组 [4, 6] 的和为 10,10 % 6 == 4,且长度为 2,满足条件。
解题思路
这道题是经典的“前缀和 + 哈希表”问题的变种(类似于 LeetCode 523. 连续的子数组和,但余数要求从 0 变成了 n)。
核心数学原理:
假设前缀和数组为 prefix,其中 prefix[i] 表示 nums[0…i-1] 的和。
子数组 nums[i…j] (其中 j ge i+1) 的和可以表示为 prefix[j+1] - prefix[i]。
题目要求:
(prefix[j+1] - prefix[i]) % k == n
变换公式:
prefix[j+1] - prefix[i] = m cdot k + n (其中 m 是整数)
prefix[j+1] - n = prefix[i] + m cdot k
这意味着 (prefix[j+1] - n) 和 prefix[i] 在模 k 的意义下同余。
即:(prefix[j+1] - n) % k == prefix[i] % k
特殊情况处理:
由于涉及负数取模(虽然题目说非负整数数组,但前缀和减 n 后可能为负,或者逻辑上需要统一处理),在 Java/C++ 中 % 运算符对负数的结果可能为负数,因此需要调整为正余数:
remainder = ((prefixSum - n) % k + k) % k
算法步骤:
使用一个哈希表 map 存储 {余数: 最早出现的下标}。
初始化 map.put(0, -1)。这里为什么是 0?
我们需要处理从数组开头开始的子数组。如果 prefix[j+1] 本身就满足 (prefix[j+1] - n) % k == 0,那么我们需要找到一个下标 -1,使得 prefix[-1] (视为0) 满足条件。
等等,让我们重新推导一下初始状态。
我们希望找到 i = 2,说明找到了长度至少为 2 的子数组,返回 true。
如果 map 中没有当前 sum % k 的余数,则存入 map.put(sum % k, j)。
注意:只存第一次出现的位置,因为我们要找最长的或者只要存在即可,且为了满足长度 ge 2,越早出现的下标越容易满足距离条件。
边界情况:
k 为 0?题目通常保证 k > 0,如果 k=0 则不能取模,需单独判断 sum == n。但根据题目描述 “k 的倍数”,通常隐含 k neq 0。若 k=0,则要求 sum == n。
n 的范围?通常 0 le n 0,但防御性编程)
// 如果 k=0,则要求子数组和严格等于 n
if (k == 0) {
int sum = 0;
for (int i = 0; i = 1) { // 长度至少为2
// 检查以 i 结尾,长度>=2 的子数组是否有和为 n 的
// 这里需要暴力或者滑动窗口,因为不能取模
// 简单起见,如果 k=0 的情况很少见,可以用双重循环检查
// 优化:维护一个滑动窗口和?不,因为元素非负,和单调递增,可以用双指针
// 但为了代码简洁,若 k=0 且数据量大,需特殊处理。
// 鉴于 LintCode 此类题目 k 通常 >= 1,此处主要关注 k != 0
}
}
// 针对 k=0 的完整逻辑(如果需要):
int currentSum = 0;
Map sumMap = new HashMap(); // 存前缀和 -> 下标
sumMap.put(0, -1);
int s = 0;
for(int i=0; i= 2) return true;
}
if(!sumMap.containsKey(s)){
sumMap.put(s, i);
}
}
return false;
}
// 哈希表存储:余数 -> 最早出现的下标
Map map = new HashMap();
// 初始化:前缀和为 0 时,下标为 -1
// 这里的余数是针对前缀和本身 mod k
map.put(0, -1);
int prefixSum = 0;
for (int i = 0; i = 2) {
return true;
}
}
// 将当前前缀和的余数存入 map,只存第一次出现的位置
int currentRemainder = prefixSum % k;
// 不需要再次调整 currentRemainder,因为 prefixSum 是非负的(nums非负)
// 但为了严谨,如果 prefixSum 可能为负(本题不会),也需要 +k %k
if (!map.containsKey(currentRemainder)) {
map.put(currentRemainder, i);
}
}
return false;
}
}
复杂度分析
时间复杂度: O(N),其中 N 是数组 nums 的长度。我们只需要遍历一次数组,哈希表的插入和查找操作平均为 O(1)。
空间复杂度: O(min(N, k)),哈希表最多存储 k 个不同的余数(或者 N 个前缀和,取决于哪个更小)。
注意事项
取模运算的负数处理:(a - b) % k 在 a-b 为负数时,Java 会返回负数。因此必须使用 ((a - b) % k + k) % k 来确保余数在 [0, k-1] 范围内。
长度限制:题目明确要求子数组长度至少为 2,所以在找到匹配的余数后,必须检查 currentIndex - storedIndex >= 2。
初始化:map.put(0, -1) 是关键,它代表了前缀和为 0 的虚拟位置,用于处理从数组第一个元素开始的子数组。
这个解法能够高效地解决 LintCode 3880 问题。
更多推荐




所有评论(0)