《算法零基础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=0∑ia[k] 将
i
−
1
i-1
i−1 代入上述的
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[i−1]=a[0]+a[1]+...a[i−1]=k=0∑i−1a[k] 于是可以得到
s
u
m
[
i
]
=
s
u
m
[
i
−
1
]
+
a
[
i
]
sum[i] = sum[i-1] + a[i]
sum[i]=sum[i−1]+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[i−1]+a[i]i=−1i≥0
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=l∑ra[k]=a[l]+a[l+1]+...a[r−1]+a[r]=(a[0]+...+a[r])−(a[0]+...+a[l−1]=k=0∑ra[k]−k=0∑l−1a[k]=sum[r]−sum[l−1]
于是,只要预先将前缀和全部求出来,后面每次询问都可以做到
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讲》(第60讲) 前缀和 线性前缀和配合哈希表