首先为什么要用前缀和?前缀和的优势在哪里?
给定一个列表[a1,a2,a3,a4,a5,a6,a7,a8,a9,a10],如果我们要求前i个元素的和,一般怎么做? python中自带的api是不是就可以基本实现
比如我们像要求前5个数的和,如下所示:
a = [1,2,3,4,5,6,7,8,9]
print(sum(a[:5]))
>> 15
sum()函数其实就是帮我们做了一次数组遍历,其时间复杂度是O(n),这在平时计算一个这样简单的前i个数的和,时间复杂度可以勉强接受,但是在解决问题时,外面在嵌套一个循环呢?那整个算法的时间复杂度是不是就一下子上来了?这是非常不可取的,那么我们用什么方法将这个值简单计算前i个数优化到O(1)复杂度呢?前缀和,只需要读取某一个位置的值,就是O(1)复杂度啊!
a = [1,2,3,4,5,6,7,8,9]
l = len(a)
s = [0]*l
s[0] = a[0]
for i in range(1,l):
s[i] = s[i-1]+a[i]
print(s)
print(s[4])
>>[1, 3, 6, 10, 15, 21, 28, 36, 45]
>>15
前缀和还有一个好处,就是如果你想求[1,2,3,4,5,6,7,8,9]中后面四个元素的和怎么办?
在常规的操作就是sum(a[5:]),但是前缀和就可以s[-1]-s[4]就ok了,美滋滋!
那么这周的周赛有一道题完美的诠释了前缀和的美妙!
最小平均差
以下就是我最先实现的一个暴力解法,不出意外完美超时了,样例能跑通80%:
class Solution:
def minimumAverageDifference(self, nums: List[int]) -> int:
if len(nums)==1:
return 0
res = 100
index = 1000
l = len(nums)
sums = sum(nums)
for i in range(l):
c = sum(nums[:i+1])
a = c//(i+1)
if i==l-1:
k = abs(a)
else:
b = (sums-c)//(l-i-1)
k = abs(a-b)
if k< res:
res = k
index = i
return index
通过上面的讲解,应该知道这个代码的问题所在了吧,就是反复调用sum()函数,就大大增加了时间复杂度啊,跑通了才怪,O(n^2)的时间复杂度,是不是需要考虑优化
前缀和的写法,就将时间复杂度简化到了O(n)
class Solution:
def minimumAverageDifference(self, nums: List[int]) -> int:
# 使用前缀和
l = len(nums)
s = [0]*l
s[0] = nums[0]
for i in range(1,l):
s[i]=s[i-1]+nums[i]
if len(nums)==1:
return 0
res = 1000000 # 更地道的写法是float('inf')
index = 1000000 # 可以写-1
l = len(nums)
for i in range(l):
a = s[i]//(i+1)
if i==l-1:
k = abs(a)
else:
b = (s[-1]-s[i])//(l-i-1)
k = abs(a-b)
if k< res:
res = k
index = i
return index
总结:如果你在解决一个问题,需要反复调用sum()这类本质上需要遍历数组的复杂度性质的api,考虑前缀和!考虑前缀和!考虑前缀和!