《算法零基础100讲》(第57讲) 前缀和 线性前缀和入门

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《算法零基础100讲》(第57讲) 前缀和 线性前缀和入门相关的知识,希望对你有一定的参考价值。

零、写在前面

  这是《算法零基础100讲》 专栏打卡学习的第五十七天了。
  每天打卡的题,做不出来没关系,因为困难的题涉及知识点较多,后面还是会开放出来的,就像昨天的 最大公约数 那道题今天还是会有,所以不要着急,内容能看懂,能自己分析,能做出简单题,就可以打卡。
  在刷题的过程中,总结自己遇到的坑点,写出 「 解题报告 」 供他人学习,也是一种自我学习的方式。这就是经典的帮助他人的同时,成就自己。目前, 「 万人千题 」 社区 每天都会有五六篇高质量的 「 解题报告 」 被我 「 加精 」。如果觉得自己有能力的,也可以来发布你的 「 解题报告 」。千万级流量,你我共同拥有。

一、概念定义

1、部分和

  所谓 部分和,就是给定一个数组,求它的某一段连续子数组的和。

2、朴素做法

  比较传统的做法,就是对于要求部分和的区间 [ l , r ] [l, r] [l,r],枚举所有的数进行相加,如下:

int partialSum(int *a, int l, int r) 
    int i;
    int s = 0;
    for(i = l; i <= r; ++i) 
        s += a[i];
    
    return s;

  函数的返回值代表了求 a[]数组的第 l l l 项 到 第 r r r 项的和,如果数组长度为 n n n,这样做的最坏时间复杂度为 O ( n ) O(n) O(n)。那么,试想一下,如果有 m m m 次询问,每次询问都是 O ( n ) O(n) O(n),时间复杂度就会变成 O ( n m ) O(nm) O(nm),有没有办法优化呢?

3、前缀和

  我们可以用一个sum[]数组来表示数组的前缀和,即sum[i]表示的是前 i i i 项的和,数学公式如下: s u m [ i ] = a [ 0 ] + a [ 1 ] + . . . a [ i ] = ∑ k = 0 i a [ k ] sum[i] = a[0] + a[1] + ... a[i] = \\sum_k=0^i a[k] sum[i]=a[0]+a[1]+...a[i]=k=0ia[k]  将 i − 1 i-1 i1 代入上述的 i i i,得到: s u m [ i − 1 ] = a [ 0 ] + a [ 1 ] + . . . a [ i − 1 ] = ∑ k = 0 i − 1 a [ k ] sum[i-1] = a[0] + a[1] + ... a[i-1] = \\sum_k=0^i-1 a[k] sum[i1]=a[0]+a[1]+...a[i1]=k=0i1a[k] 于是可以得到 s u m [ i ] = s u m [ i − 1 ] + a [ i ] sum[i] = sum[i-1] + a[i] sum[i]=sum[i1]+a[i]

4、前缀和的边界值

  有了递推式,我们还需要有一个边界值,从定义出发,边界值应该是: s u m [ − 1 ] = 0 sum[-1] = 0 sum[1]=0 怎么理解呢?试想一下, s u m [ 1 ] sum[1] sum[1] 表示的是从 第0项 累加到 第1项; s u m [ 0 ] sum[0] sum[0] 表示的是从 第0项 累加到 第0项; s u m [ − 1 ] sum[-1] sum[1] 表示的是一项都没有累加,那么这个值固然就是零了。即: s u m [ i ] = 0 i = − 1 s u m [ i − 1 ] + a [ i ] i ≥ 0 sum[i] = \\begincases 0 & i = -1\\\\ sum[i-1] + a[i] & i \\ge 0\\endcases sum[i]=0sum[i1]+a[i]i=1i0

5、边界处理

  这时候,我们需要注意 C 语言中的 下标是从零开始的,所以, s u m [ − 1 ] sum[-1] sum[1]会导致数组下标越界,可以将它转换成函数的形式将数组sum[]进行一次封装:

int prefixSum(int n) 
    if(n == -1) 
        return 0;
    
    return sum[n];

6、再看部分和

  然后,我们继续来看部分和,有了前缀和数组sum[]以后,我们就可以利用差分法,在 O ( 1 ) O(1) O(1) 的时间内求得部分和,原因就是: ∑ k = l r a [ k ] = a [ l ] + a [ l + 1 ] + . . . a [ r − 1 ] + a [ r ] = ( a [ 0 ] + . . . + a [ r ] ) − ( a [ 0 ] + . . . + a [ l − 1 ] = ∑ k = 0 r a [ k ] − ∑ k = 0 l − 1 a [ k ] = s u m [ r ] − s u m [ l − 1 ] \\beginaligned \\sum_k=l^ra[k] &= a[l] + a[l+1] + ... a[r-1] + a[r] \\\\ &= (a[0]+...+a[r]) - (a[0]+...+a[l-1] \\\\ &= \\sum_k=0^ra[k] - \\sum_k=0^l-1a[k] \\\\ &= sum[r] - sum[l-1] \\endaligned k=lra[k]=a[l]+a[l+1]+...a[r1]+a[r]=(a[0]+...+a[r])(a[0]+...+a[l1]=k=0ra[k]k=0l1a[k]=sum[r]sum[l1]
  于是,只要预先将前缀和全部求出来,后面每次询问都可以做到 O ( 1 ) O(1) O(1)

二、题目描述

1、定义

  数组 中心下标 是数组的一个下标,其 左侧所有元素相加的和 等于 右侧所有元素相加的和

2、求解

  给定一个整数数组 nums,请计算数组的 中心下标 。如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1

三、算法详解

  考虑以下几个点即可:
  1、首先枚举一个点,统计它左边 所有数的和右边所有数的和
  2、一旦找到一个满足条件的点,立刻返回;
  3、统计和的过程可以采用 前缀和,这样可以在 O ( 1 ) O(1) O(1) 完成,总的时间复杂度为 O ( n ) O(n) O(n)

四、源码剖析

#define maxn 100010
int sum[maxn];

int pivotIndex(int* nums, int numsSize)
    int i;
    for(i = 0; i < numsSize; ++i) 
        sum[i] = nums[i];
        if(i) 
            sum[i] += sum[i-1];        // (1) 
    
    if(sum[numsSize-1] - sum[0] == 0) 
        return 0;                      // (2) 
    
    for(i = 1; i < numsSize; ++i) 
        if( sum[i-1] == sum[numsSize-1] - sum[i] ) 
            return i;                  // (3) 
        
    
    return -1;                         // (4) 

  • ( 1 ) (1) (1) 计算前缀和;
  • ( 2 ) (2) (2) 考虑 中心下标 在 0 的情况;
  • ( 3 ) (3) (3) 左边的部分和为 sum[i-1],右边的部分和为 sum[numsSize-1] - sum[i]
  • ( 4 ) (4) (4) 找不到中心坐标的情况;

五、推荐专栏

🌳《画解数据结构》🌳

画解顺序表

六、习题练习

序号题目链接难度
1所有奇数长度子数组的和★☆☆☆☆
2找到最高海拔★☆☆☆☆
3逐步求和得到正数的最小值★☆☆☆☆
4一维数组的动态和★☆☆☆☆
5寻找数组的中心下标★★☆☆☆
6 区域和检索 - 数组不可变★★☆☆☆
👇🏻 添加 博主 参加九日集训👇🏻

以上是关于《算法零基础100讲》(第57讲) 前缀和 线性前缀和入门的主要内容,如果未能解决你的问题,请参考以下文章

《算法零基础100讲》(第59讲) 前缀和 线性前缀和统计

《算法零基础100讲》(第60讲) 前缀和 线性前缀和配合哈希表

《算法零基础100讲》(第61讲) 前缀和 二维前缀和

《算法零基础100讲》(第17讲) 线性枚举 - 最值算法

题解《算法零基础100讲》(第17讲) 线性枚举 - 最值算法(java版)

题解《算法零基础100讲》(第18讲) 线性枚举 - 统计法入门(java版)