最近做题的时候,遇到了很多求数组中连续子数组值的和问题,简单点的我还能通过滑动窗口等方法解决掉,后来发现越来越困难,然后看到网上的很多解析,发现很多都是通过一种新的解题思路:前缀和的思路去解题,然后我用该方法做了几道题,略有所感,下面把思路和通过题目实际去解决的过程给大家分享一下。
1.什么是前缀和
顾名思义,是要求前缀的总和。对于一个存放数字的数组而言,前缀就是指的数组的前k项,因此对应的前缀和就是数组前k项的和。
前缀和一般用来求数组中连续子数组的值的和。
前缀和的题目解题思维比较固定,即当我们循环数组到下标N时,需要用到数组前N-1项的计算的结果(这里不一定非要是和,也可能是积等),此时我们就该考虑是否应该通过计算数组循环过程中的累计值的方式简化解题,如此便有了前缀和的解题思想。
同时为了优化查询速度,我们保存前缀和的方式采用HashMap,因为HashMap已经做得足够优秀。《Map到HashMap的一步步实现》
2.实战
我就通过一道比较经典的题目来带着大家走一遍这个思路
2.1 题目描述
《剑指 Offer II 010. 和为 k 的子数组》
给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。
2.2 题目示例
2.3 解题思路
计算该数组的前缀和,然后由当前位置的前缀和减去K,得到它期望出现的序列前面的前缀和,如果有则count加上这个期望的前缀和出现的次数。
我们定义pre[i]表示[0,i]的所有数的总和,则pre[i]可以由pre[i-1]递推而来,那么就有:
pre[i]=pre[i−1]+nums[i]
我们假设[j…i]这个子数组和为k,则这个条件我们可以转化为
pre[i]−pre[j−1]=k
简单移项可得符合条件的下标 j 需要满足:
pre[j−1]=pre[i]−k
所以我们考虑以 i 结尾的和为 k 的连续子数组个数时只要统计有多少个前缀和为pre[i]-k即可。
我们建立哈希表 mp,以和为键,出现次数为对应的值,记录pre[i] 出现的次数,从左往右边更新 mp 边计算答案,那么以 i 结尾的答案:mp[pre[i]-k]
由于pre[i] 的计算只与前一项的答案有关,因此我们可以不用建立pre 数组,直接用 pre 变量来记录pre[i−1] 的答案即可。
所以我们需要做的就只有两个操作,就是计算pre,然后使用pre-k,判断是否有和pre-k相等的pre,所以我们需要保存每一次累加的pre
整体思路如下图所示:
2.4 代码实现
public int subarraySum(int[] nums, int k) {
int count = 0;//和为k的子数组个数
int pre = 0;//前缀和
Map<Integer, Integer> mp = new HashMap<>();
//默认插入一个{0:1}的键值对,用于解决从数组开头的连续子数组满足题意的特殊场景
mp.put(0, 1);
for (int i = 0; i < nums.length; i++) {
pre += nums[i];
int diff = pre - k;
if (mp.containsKey(diff) ) {
count += mp.get(diff);
}
mp.put(pre, mp.getOrDefault(pre, 0) + 1);
}
return count;
}