⭐️引言⭐️

             大家好啊,我是执梗。前面三天讲的都是比较经典的三个图论算法,还是需要有一定的算法基础才能看懂。但是今天,我们要讲的是基础算法之一的——前缀和。非常实用且容易的一个算法,今天我们先讲基础的一维前缀和,帮助小白更好理解。

⭐️目录⭐️

​1.背景小故事​

​ 2.什么是前缀和数组?​

​3.如何预处理得到前缀和数组​

​4.前缀和数组的使用​

​5.前缀和模板题练习​

​           1)、区域和检索​

​           2)、连续的子数组和​

​           3)、连续数组​


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个元素的和


       零基础学算法100天第4天——前缀和(基础算法)_leetcode

       如该图,上面的数组为原数组,则下面的数组为改数组对应的前缀和数组。

3.如何预处理得到前缀和数组

          首先,我们要知道,前缀和数组s的下标含义s[i]是前i个元素之和。则s[i-1]的含义是前i-1个元素之和,此时我们在s[i-1]的基础上,所以我们可以得知:

          零基础学算法100天第4天——前缀和(基础算法)_leetcode_02

         但要注意一个问题,一般我们的前缀和数组s[0]一定是等于0的,代表前0个元素之和为0。而且也是为了防止出现下标越界的问题,比如当公式内i等于0时,后面就会出现越界。

        还有一个很重要的问题。

        这个预处理公式并不是一成不变的,这是因为原数组的首位数有可能是下标从0开始,也有可能下标从1开始。当下标从0开始时,原数组第i个元素是a[i-1],当下标从1开始时,原数组第i个元素是a[i]。

        所以在原数组下标为1的情况下,求前缀和的公式也可能是这样:

        零基础学算法100天第4天——前缀和(基础算法)_蓝桥杯_03

        大家应该灵活变通。

 4.前缀和数组的使用

         我们通过预处理得到了前缀和数组,那么如何使用它呢?

         我们还是通过前缀和的含义出发,假设我们要得到第x个元素到第y个元素之和。而s[y]的值是前y个元素之和,s[x-1]的含义是前n-1个元素之和,两者作差即可得到第x个元素到第y个元素之和。

        公式为:零基础学算法100天第4天——前缀和(基础算法)_蓝桥杯_04

        这里也体现了为什么前缀和的下标要从1开始,如果从0开始,那么在x-1时又容易产生越界问题。

5.前缀和模板题练习

           1)、区域和检索


零基础学算法100天第4天——前缀和(基础算法)_数据结构_05

 题目链接:​​区域和检索​


             实打实的一维前缀和裸题,我们以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)、连续的子数组和


零基础学算法100天第4天——前缀和(基础算法)_数组_06题目链接:​​​连续的子数组和​​                


           题目分析,首先我们要知道同余定理。也就是:

           零基础学算法100天第4天——前缀和(基础算法)_leetcode_07

            如果我们想找到一段子数组的和是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;
}
}

        如果文章对你有所帮助,还望能给点三连支持一下,非常感谢!!!

零基础学算法100天第4天——前缀和(基础算法)_数组_08