前缀和算法:和为 K 的子数组与和可被 K 整除的子数组
前缀和算法用于解决和为 K 的子数组及和可被 K 整除的子数组问题。核心思路是利用哈希表存储前缀和或前缀和余数的出现次数,避免暴力枚举。对于和为 K 的问题,寻找 sum[i] - k 的前缀和;对于整除问题,利用同余定理判断余数是否相同。需注意 C++ 中负数取模的修正。代码包含 C++ 和 Java 实现,时间复杂度 O(n),空间复杂度 O(n)。

前缀和算法用于解决和为 K 的子数组及和可被 K 整除的子数组问题。核心思路是利用哈希表存储前缀和或前缀和余数的出现次数,避免暴力枚举。对于和为 K 的问题,寻找 sum[i] - k 的前缀和;对于整除问题,利用同余定理判断余数是否相同。需注意 C++ 中负数取模的修正。代码包含 C++ 和 Java 实现,时间复杂度 O(n),空间复杂度 O(n)。

力扣链接:560. 和为 K 的子数组
题目描述:

参考下图:

设为数组中的任意位置,用 sum[i] 表示 [0 , i] 区间内所有元素的和。
如果想知道有多少个【以 i 结尾的和为 k 的子数组】,就要找到有多少个起始位置为 x1,x2,x3...使得 [x , i] 区间内的所有元素的和为 k。那么 [0 , x] 区间内的和是不是就是 sum[i] - k 了。于是问题就变成——
找到在 [0 , i - 1] 区间内,有多少前缀和等于 sum[i] - k 的即可。
我们不需要真的去初始化一个前缀和数组,因为我们只关心在 i 位置之前,有多少个前缀和会等于 sum[i] - k。因此,我们仅需用一个哈希表,一边求当前位置的前缀和,一边存下之前每一种前缀和出现的次数。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map <int,int> hash; // 统计前缀和出现的次数
// 细节 hash[0] = 1; // 用一个变量标记一下前缀和
int sum = 0, ret = 0;
for(auto x : nums) {
sum += x; // 计算当前位置的前缀和
if(hash.count(sum - k)) ret += hash[sum - k];
hash[sum]++;
}
return ret;
}
};
时间复杂度:O(n),空间复杂度:O(n)。

// Java 写法
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> hash = new HashMap<Integer, Integer>();
hash.put(0, 1);
int sum = 0, ret = 0;
for (int x : nums) {
sum += x; // 计算当前位置的前缀和
ret += hash.getOrDefault(sum - k, 0); // 统计结果
hash.put(sum, hash.getOrDefault(sum, 0) + 1); // 把当前的前缀和丢到哈希表里面
}
return ret;
}
}
时间复杂度:O(n),空间复杂度:O(n)。

本题是某一年的蓝桥杯竞赛原题。
力扣链接:974. 和可被 K 整除的子数组
题目描述:

暴力解法就是枚举出所有的子数组的和,这里不再赘述。
如果 (a - b)% n == 0,那么我们可以得到一个结论:a % n == b % n。用文字叙述就是,如果两个数相减的差能被 n 整除,那么这两个数对 n 取模的结果相同。
例如:(26 - 2) %12 == 0,那么 26 %12 == 2 % 12 == 2。
(1)c++ 中关于负数的取模运算,结果是【把负数当成正数,取模之后的结果加上一个负号】。
例如:-1 % 3 = (-1 % 3) = -1
(2)因为有负数,为了防止发生【出现负数】的结果,以(a%n+n)%n 的形式输出保证为正。
例如:-1 % 3 =(-1 % 3 + 3) % 3 = 2
思路与 560.和为 K 的子数组 这道题的思路相似。
还是用这张图——

设为数组中的任意位置,用 sum[i] 表示 [0,i] 区间内所有元素的和。
1、想知道有多少个「以 i 为结尾的可被 k 整除的子数组」,就要找到有多少个起始位置为 x1,x2,x3...使得 [x,i] 区间内的所有元素的和可被 k 整除。
2、设 [0,x - 1] 区间内所有元素之和等于 a,[0,i] 区间内所有元素的和等于,可得 (b - a) % k == 0。
3、由同余定理可得,[0,x - 1] 区间与 [0,i] 区间内的前缀和同余。于是问题就变成——
(1)找到在 [0,i-1] 区间内,有多少前缀和的余数等于 sum[i] % k 的即可。
我们不用真的初始化一个前缀和数组,因为我们只关心在 i 位置之前,有多少个前缀和等于 sum[i] - k。因此,我们仅需用一个哈希表,一边求当前位置的前缀和,一边存下之前每一种前缀和出现的次数。
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int,int> hash; // 统计前缀和出现的次数
// 处理细节
hash[0] = 1; // 这里存的依旧是余数,只不过 0 % k 还是 0,所以这里存的是 0 这个数的余数
int sum = 0, ret = 0;
for(auto x : nums) {
sum += x; // 算出当前位置的前缀和
// 求出修正后的余数
int r = (sum % k + k) % k;
if(hash.count(r)) ret += hash[r]; // 统计结果
hash[r]++; // 继续遍历
}
return ret;
}
};
时间复杂度:O(n),空间复杂度:O(n)。

// Java 写法
class Solution {
public int subarraysDivByK(int[] nums, int k) {
Map<Integer, Integer> hash = new HashMap<Integer, Integer>();
hash.put(0 % k, 1);
int sum = 0, ret = 0;
for (int x : nums) {
sum += x; // 计算当前位置的前缀和
int r = (sum % k + k) % k;
ret += hash.getOrDefault(r, 0); // 统计结果
hash.put(r, hash.getOrDefault(r, 0) + 1);
}
return ret;
}
};
时间复杂度:O(n),空间复杂度:O(n)。


微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online