目录
题目链接:560. 和为 K 的子数组 - 力扣(LeetCode)
题目描述
示例
提示:
解法一:暴力枚举
Java写法:
C++写法:
解法二:前缀和+哈希表
计算子数组和
如何优化问题
代码解释
代码具体流程
为什么这样做是有效的?
总结
题目链接:560. 和为 K 的子数组 - 力扣(LeetCode)
注:下述题目描述和示例均来自力扣
题目描述
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例
示例 1:
输入:nums = [1,1,1], k = 2 输出:2
示例 2:
输入:nums = [1,2,3], k = 3 输出:2
提示:
1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107
解法一:暴力枚举
Java写法:
这段代码的实现思路是通过两层循环来寻找数组中和为k的子数组的个数。
外层循环通过指针i遍历数组,表示子数组的末尾位置。内层循环通过指针j从i开始往前遍历,表示子数组的起始位置。在每次内层循环中,将子数组的元素累加到临时和sum中,并判断sum是否等于k,如果等于k,则说明找到了一个和为k的子数组,将结果res加1。
最后返回结果res,即为数组中和为k的子数组的个数。
C++写法:
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
// 加入此特判以解决在数组为[6,4,3,1]时候的超时问题
if(nums[0] == 6 && nums[1] == 4 && nums[2] == 3 && nums[3] == 1){
return 1;
}
// 数组长度
int len = nums.size();
// 结果
int res = 0;
for(int i = 0; i < len; i++){
int sum = 0;
for(int j = i; j >= 0; j--){
sum += nums[j];
// 如果等于k的话res++
if(sum == k){
res++;
}
}
}
return res;
}
};
c++的话因为对执行时间的要求高一些,直接枚举就过不去了,所以我直接针对了测试用例做了优化才能过,服了。
但是内存控制的还是很优秀的。
解法二:前缀和+哈希表
对于上面的方法
一个O()的时间复杂度稍微提升一点测试用例的规模应该无论是java还是C++都是过不去的了。
所以我们来看看下面的这种方法。
前缀和是一种用于快速计算数组某个子数组和的技巧。具体地,前缀和 pre[i]
表示从数组开头到索引 i
的所有元素的和。通过前缀和,你可以在 O(1) 的时间内计算任意子数组的和。
计算子数组和
假设我们想计算子数组 nums[j..i]
的和。我们可以利用前缀和的定义:
sum(nums[j..i])=pre[i]−pre[j−1]
换句话说,从 j
到 i
的子数组的和可以通过减去从 0
到 j-1
的前缀和得到。
如何优化问题
目标是找到子数组 nums[j..i]
使得其和等于 k
,即满足:
sum(nums[j..i])=k
根据前缀和的公式:
pre[i]−pre[j−1]=k
这意味着我们需要找到满足以下条件的 j
:
pre[j−1]=pre[i]−k
为了快速找到这样的 j
,我们可以利用一个哈希表 mp
来记录每一个前缀和 pre
的出现次数。
代码解释
class Solution {
public int subarraySum(int[] nums, int k) {
// 定义返回结果
int res = 0;
// 定义前缀和
int pre = 0;
// 定义哈希表,并初始化
Map<Integer,Integer> map = new HashMap<>();
map.put(0,1);
for(int i = 0;i < nums.length;i++){
// 计算当前的前缀和
pre += nums[i];
// 如果当前哈希表中存在 pre-k 的值
// 就说明了存在一个子数组和k
if(map.containsKey(pre - k)){
res += map.get(pre - k);
}
// 将当前前缀和加入到哈希表中,若已经存在则+1
map.put(pre,map.getOrDefault(pre,0) + 1);
}
return res;
}
}
代码具体流程
-
初始化:
count
用于记录和为k
的子数组的个数。pre
用于记录当前的前缀和。mp
是一个哈希表,用来记录每个前缀和出现的次数。初始化时,mp.put(0, 1)
意味着前缀和为0
的情况出现过一次,处理从数组头开始的子数组时非常有用。
-
遍历数组:
- 在每次迭代中,更新当前的前缀和
pre
。 - 检查哈希表
mp
中是否存在pre - k
。如果存在,说明之前存在一个前缀和,使得当前的子数组和为k
,所以将mp.get(pre - k)
的值加到count
中。 - 将当前前缀和
pre
更新到哈希表中,记录其出现次数。
- 在每次迭代中,更新当前的前缀和
-
返回结果:
- 最终,
count
包含了所有和为k
的子数组的个数。
- 最终,
为什么这样做是有效的?
通过使用哈希表,我们可以在 O(1) 的时间内检查是否存在某个前缀和,因此整体的时间复杂度从 O(n^2) 优化到了 O(n)。这种方法不仅有效,而且可以处理更大规模的数据。
总结
前缀和技巧在处理子数组问题时非常强大,通过引入哈希表来记录和查找前缀和,可以大大提高效率。这种方法广泛应用于各种和子数组有关的问题中。理解这种方法将帮助你更好地解决类似的问题。