开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情
今天继续Codility的Lessons部分的第5个主题——Prefix Sums。
Codility - Lessons - Prefix Sums
还是按先总结红色部分PDF,然后再完成蓝色的Tasks部分的节奏来~
PDF Reading Material
本部分的PDF还是只有两部分:
-
正文:1.介绍prefix sums的概念,以及计算prefix sums的方法和复杂度,即从数组第一个元素循环到最后一个即可完成截至每个元素的prefix sum的计算,时间复杂度为O(n)。2.介绍与之相对的suffix sums的概念。3.最后,介绍两种sums的主要应用场景:即计算好了prefix sums/suffix sums数组后,回答原始数组中第X到第Y个元素的加合是多少,只需要使用计算好的prefix sums数组做一个相减运算即可->P[Y+1]-P[X]
-
Exercise:是一个采蘑菇的场景,在一个长度为n的数组里,每一位都是一个随机的自然数,代表这一点上蘑菇的数量。假设起始点在数组中的某个位置k,并且一共可以自由移动m步,问最多可以采多少蘑菇。使用上边正文介绍的先计算prefix sums,再计算出最终结果的思路带入到这个场景里即可解决问题,时间复杂度为O(m+n)
Tasks
- Task1: PassingCars
题目输入是Array A,每个点代表一辆车,值为0代表travel to the east,值为1代表travel to the west。需要返回的输出值,即在A中有多少个(P,Q)组合是满足P=0,Q=1的。并且P一定在Q的左侧。
我们可以循环A数组中每一个值为0的位置作为P,并计算符合条件的Q有多少,然后再加总起来。而对于某个P(假设index=i),对应符合条件的Q有多少,就可以借助事先计算好的prefix_sums数组,利用prefix_sums[-1]-prefix[i+1]实现。
def calc_prefix_sums(A):
n = len(A)
P = [0] * (n + 1)
for k in range(1, n + 1):
P[k] = P[k - 1] + A[k - 1]
return P
def solution(A):
prefix_sums = calc_prefix_sums(A)
# 初始化pairs为0,p_last是计算好的prefix_sums中的最后一位
pairs = 0
p_last = prefix_sums[-1]
# 循环A中元素,遇到0时(P车travelling to the east),计算符合Q=1(Q车travelling to the west)的点数量
for i, a in enumerate(A):
if a==0:
# 符合Q=1(Q车travelling to the west)的点数量,就是prefix_sums[-1]-prefix_sums[i+1]
pairs += (p_last - prefix_sums[i+1])
# 根据题干条件,返回pairs或-1
if pairs>1000000000:
return -1
return pairs
- Task2: CountDiv
本题的输入是A,B,K三个整数,其中A<=B。需要返回的是在A~B间(包含A、B)的全部整数里,一共有几个可以被X整除。
这题和prefix_sums的关联其实我没太想清楚,按我的解题思路,这就是一个很简单的把A和B分别与K相除取整数和余数(k1X+b1=A,k2X+b2=B),然后再把两个k做差的事情,然后针对b1是否为0,会决定k2-k1后是否需要+1。
def solution(A, B, K):
# for A and B, y = NX + b
N_A, b_A = A//K, A%K
N_B, b_B = B//K, B%K
# A%K==0和!=0时,满足条件的X个数会有1的差异
if b_A==0:
return N_B - N_A + 1
else:
return N_B - N_A
- Task3: GenomicRangeQuery
本题输入是1个代表DNA的字符串S,长度为N。和2个长度都为M的非空数组P、Q。对于P、Q中每组同index的值pi、qi,需要计算DNA中pi~qi这个片段的minimal impact factor,也就就是片段中所有nucleotide的impact factor里的最小值,每个nucleotide的impact factor具体为:A=1,C=2,G=3,T=4。
最终需要的输出是一个Array,每个index下的值,就是S的P[index]~Q[index]这段DNA片段的minimal impact factor。
本题解题思路也是注释就可以写明白,首次构建一个代表每个nucleotide的impact_factor_dict,然后直接迭代P、Q中的每对元素确定具体片段(S[p:q]),接着按照impact factor从小到大顺序,依次确认片段中是否包含A/C/G/T字符,判断是包含时就可以直接跳出了:
def solution(S, P, Q):
imp_fac_dict = {'A':1, 'C':2, 'G':3, 'T':4}
# 迭代P、Q中元素,计算min_impact_factor,并存入要返回的Array(ans_arr)中
ans_arr = [0 for p in P]
for i, (p,q) in enumerate(zip(P,Q)):
for k in imp_fac_dict:
if k in S[p:q+1]:
ans_arr[i] = imp_fac_dict[k]
break
return ans_arr
- Task4: MinAvgTwoSlice
本题的输入为包含N个自然数的Array A,需要输出的是可以得到Minimal AvgSlice的起始点index。AvgSlice的算法,是确定了起始点之后,其之后的至少包含2个元素的全部Slice的几何均值。也就是说,比如N=7,起始点为index=0,那此时对应的所有AvgSlice,就包含以下6个Slice的几何均值:【index=0~1,0~2,0~3,0~4,0~5,0~6】,然后Minimal AvgSlice就是这6个Slice的几何均值里最小的数。
这题的解法我想了很久也没想出来,最后不得已参考了谷歌。我看懂的解题思路,似乎还是和prefix_sums没什么关系:
首先,确定MinAvgSlice一定是2个元素或3个元素的Slice,因为如果长于3个元素,就一定可以分为两个Slice A、B,而无论Avg(Slice A)是大于、等于、还是小于Avg(Slice B),Slice(A+B)一定大于等于min(Avg(Slice A),Avg(Slice B))。
然后就可以在A里从index=0~index=N-2循环一遍,分别计算length=2/3的Slice的几何均值。再用2个常数分别记录出现过的MinAvgSlice值,及对应的MinAvgSlice的index,最终循环结束返回MinAvgSlice的index即可:
def solution(A):
# 使用A[:2],初始化一个min_v和min_v_idx
min_v = sum(A[:2])/2
min_v_idx = 0
# 迭代数组A,从index=0~index=N-2
N = len(A)
for i in range(N-1):
# MinAvgSlice只会是length=2/3的Slice,且index=N-2时,只存在length=2的Slice
if i==N-2:
ls = (2,)
else:
ls = (2,3)
for l in ls:
tmp_v = sum(A[i:i+l])/l
if tmp_v<min_v:
min_v = tmp_v
min_v_idx = i
return min_v_idx