⭐️引言⭐️
大家好啊,我是执梗。前面三天讲的都是比较经典的三个图论算法,还是需要有一定的算法基础才能看懂。但是今天,我们要讲的是基础算法之一的——前缀和。非常实用且容易的一个算法,今天我们先讲基础的一维前缀和,帮助小白更好理解。
⭐️目录⭐️
2.什么是前缀和数组?
1.背景小故事
小兔子兔八哥家里有超级多的萝卜坑,每个坑里都有一定的萝卜数量。兔八哥的妈妈为了考验兔八哥,要求每次给它两个坑位x和y,让它在一定时间内告诉自己从x坑到y坑一共有多少个萝卜呢?兔八哥每次都要跑到坑位x数有多少个萝卜,再跑去x+1坑位....直到y,当他兴高采烈的得到答案回家时,妈妈却告诉它——你超时啦!
为什么会超时呢?
兔八哥冥思苦想这个问题,每次自己都跑去萝卜坑一个个数,真是太笨了,为什么不拿个本子去记下来呢?兔八哥懊恼的拍了拍脑袋,可是再一想,每次妈妈给定x,y。我还是得用坑x+坑x+1+坑x+2一直到坑y。这太慢了!妥妥的O(n)的时间复杂度。
直到兔八哥的朋友小黑兔来了以后,兔八哥向他说了自己的烦恼,小黑说这还不简单?于是两人拿了本子去了萝卜坑。先用数组a记录了每个萝卜坑的数量,a[i]表示第i个坑的萝卜数量。然后小黑一阵计算以后给了兔八哥一个数组S,告诉小黑告诉兔八哥每次妈妈告诉你x和y后,用S[y]-S[x-1]就是答案啦。
兔八哥照做以后,在妈妈念出x和y后下一秒就能给出答案,妥妥的O(1)时间复杂度。而这个神秘的数组S——就是前缀和数组!
2.什么是前缀和数组?
其实通过背景小故事,就能很清楚的感受到什么是前缀和数组,以及它的使用场景。它是一种通过对原静态数组预处理得到一个数组,该数组可以用于快速求出原数组的一段区间和。
原数组a[i]的含义——第i个元素的值
a[i]的前缀和数组s[i]的含义——数组a中前i个元素的和
如该图,上面的数组为原数组,则下面的数组为改数组对应的前缀和数组。
3.如何预处理得到前缀和数组
首先,我们要知道,前缀和数组s的下标含义s[i]是前i个元素之和。则s[i-1]的含义是前i-1个元素之和,此时我们在s[i-1]的基础上,所以我们可以得知:
但要注意一个问题,一般我们的前缀和数组s[0]一定是等于0的,代表前0个元素之和为0。而且也是为了防止出现下标越界的问题,比如当公式内i等于0时,后面就会出现越界。
还有一个很重要的问题。
这个预处理公式并不是一成不变的,这是因为原数组的首位数有可能是下标从0开始,也有可能下标从1开始。当下标从0开始时,原数组第i个元素是a[i-1],当下标从1开始时,原数组第i个元素是a[i]。
所以在原数组下标为1的情况下,求前缀和的公式也可能是这样:
大家应该灵活变通。
4.前缀和数组的使用
我们通过预处理得到了前缀和数组,那么如何使用它呢?
我们还是通过前缀和的含义出发,假设我们要得到第x个元素到第y个元素之和。而s[y]的值是前y个元素之和,s[x-1]的含义是前n-1个元素之和,两者作差即可得到第x个元素到第y个元素之和。
公式为:
这里也体现了为什么前缀和的下标要从1开始,如果从0开始,那么在x-1时又容易产生越界问题。
5.前缀和模板题练习
1)、区域和检索
题目链接:区域和检索
实打实的一维前缀和裸题,我们以arr为前缀和数组,先预处理好,然后直接在函数中使用即可,注意这里给定的原数组下标是从0开始。
class NumArray {
int[] arr;
public NumArray(int[] nums) {
int n=nums.length;
arr=new int[n+1];
for(int i=0;i<n;i++){
arr[i+1]=arr[i]+nums[i];
}
}
public int sumRange(int left, int right) {
return arr[right+1]-arr[left];
}
}
2)、连续的子数组和
题目链接:连续的子数组和
题目分析,首先我们要知道同余定理。也就是:
如果我们想找到一段子数组的和是k的倍数,也就说明我们要在前缀和数组中找到两个和为a和b的满足以上式子的关系,同时得保证两者的距离不能小于2。由于是回答是否存在,不需要找出具体的子数组,所以我们可以用set存储前缀和数组中每个元素对k取余的值。
代码转换:
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int n=nums.length;
if(n<2) return false;
int[] arr=new int[n+1];
for(int i=0;i<n;++i){
arr[i+1]=arr[i]+nums[i];
}
Set<Integer> set=new HashSet<>();
for(int i=2;i<n+1;i++){
set.add(arr[i-2]%k);
if (set.contains(arr[i]%k)) return true;
}
return false;
}
}
3)、连续数组
给定一个二进制数组
nums
, 找到含有相同数量的 0
和 1
的最长连续子数组,并返回该子数组的长度。题目链接:连续数组
这道题虽然不是前缀和裸题,却是运用了前缀和的思想,我们知道:数组中只有0和1两个数字,如果我们用变量count记录,遇到0时count--,遇到1时count++。如果一段区间0和1的个数一样,那么count在这两个位置的值就一样,我们用Map来记录,count值为key,下标为value。当遍历到某个位置时,如果前面某个位置的count值和此时位置一样,说明两点区间的0和1个数一样,更新答案的最大值。
class Solution {
Map<Integer,Integer> map=new HashMap();
//记录答案
int res=0;
int count=0;
public int findMaxLength(int[] a) {
int n=a.length;
map.put(0,-1);
for(int i=0;i<n;++i){
if(a[i]==0) --count;
else count++;
if(map.containsKey(count)){
res=Math.max(res,i-map.get(count));
}else{
map.put(count,i);
}
}
return res;
}
}
如果文章对你有所帮助,还望能给点三连支持一下,非常感谢!!!