今天继续Codility的Lessons部分的第15个主题——Caterpillar method
Codility - Lessons - Caterpillar method
还是按先总结红色部分PDF,然后再完成蓝色的Tasks部分的节奏来~
PDF Reading Material
- 前言: 介绍了Caterpillar方法的思路,就好像模拟一只毛毛虫,毛毛虫沿着数组向前走,而我们时刻维护着毛毛虫的首和尾的index信息,最终完成对原始需求的判断。
- 15.1: 题目为:判断在一个a0,a1,...,an的序列中,是否包含的sum值为s的子序列。解法用了Caterpillar思想,设定front和back两个头尾变量,然后在对back从1~len(序列)的过程中,探索是否存在使得back~front之间子序列总和等于s的子序列。由于借助了Caterpillar的思想,对于front的确认不用每次都从back开始,整体计算的时间复杂度仅仅是O(n)。
- 15.2: 题目为:判断在提供的一个代表了n个棍子的递增序列中,一共可以组成多少组triangle。这里的思想跟15.1例题中的十分相似,只是15.1只是从1~len(序列)循环back,而本题是循环所有的三角形两边(x,y)组合,并在对每组(x,y)组合确认z的过程中用到Caterpillar思想,因此最终整体计算的时间复杂度是O(n^2)。
Tasks
AbsDistinct: Compute number of distinct absolute values of sorted array elements.
题目概括:本题输入是一个递增的Array A,但是其中元素的值可能会有重复,需要返回的,是distinct absolute values的个数,也就是count(distinct abs(A))。
解法概括:解法是初始化一个pos_end和pos_end_v,result初始化为0,然后循环Array A中的每个数字a,并进行是否要对result+1的判断。 其中neg_eng,是用来记录最后一个非正数的index,以便在后续循环正数的过程中,确定在负数中是否已经计数过绝对值与当前正数相等的数字。pos_end_v,是用来在进入正数部分的循环时,记录最后一次的正数值,以便判断是否与当前循环到的正数相等。
def solution(A):
neg_end = -1
pos_end_v = -1
result = 0
for i,a in enumerate(A):
if a<=0:
if neg_end==-1 or a>A[neg_end]: # 不同的负数
result+=1
neg_end+=1
else:
if a==pos_end_v: # 跟上一个pos_end一样,则直接继续循环下一个a
continue
else:
pos_end_v=a
# 再跟neg side的数字进行判断
while neg_end>0 and -A[neg_end]<a:
neg_end -= 1
if -A[neg_end]!=a:
result += 1
return result
CountDistinctSlices: Count the number of distinct slices (containing only unique numbers).
题目概括:本题输入是一个正整数M,和一个包含N个非负整数的Array A,A中每个元素都不会大于M。需要返回的输出,是A中一共有多少个子序列是distinct slices,也就是不包含重复元素的序列,且子序列最小长度可以为1。
解法概括:本题利用Caterpillar思路的解题思路还是比较容易想到的:一开始设置begin=end=0,然后end不断后移直到出现value重复,然后再不断后移begin,直到不再重复,然后再继续移动end,循环往复。最终当end走到Array A的末尾后,再继续移动begin直到也走到Array A的末尾。
移动过程中,distinct slices的计数在每次准备向后移动begin时进行(给result增加end-begin),这样可以推出,在最后begin=end都走到Array A的末尾时,总result即为想要的值。当然根据题意,还要特别处理下result>10e的场景,直接返回10e即可。
def solution(M, A):
begin, end, N = 0, 0, len(A)
v_set = set([])
result = 0
while end<N: # end走到最后一位
end_v = A[end]
while end_v in v_set:
result += (end-begin)
v_set.remove(A[begin])
begin += 1
v_set.add(end_v) # 更新当前end_v到v_set,以便后续继续判断distinct value
end += 1
# begin最后要走到end处才算结束
while begin<end:
result += (end-begin)
begin += 1
# 超10e的result,只需返回10e
return result if result<=1000000000 else 1000000000
CountTriangles: Count the number of triangles that can be built from a given set of edges.
题目概括:输入是一个Array A,其中一共有N(N<1000)个正整数,每个正整数的取值区间为[1,10e],需要返回的result,是A中可以组成三角形三边的组合的总数量。
解法概括:本题其实就是PDF Reading Matrials中15.2例题的翻版,差异只是在于15.2中的输入Array A,就是单调递增的,但是本题的初始输入Array A是乱序的。因此只要初始将A进行一个排序,后续的判断过程就直接用15.2的解答就可以了。
因为本身Caterpillar思想下,triangles部分的解决是O(n^2)复杂度的算法,因此排序部分不要高于这个复杂度即可,所以直接用Python自带的平均复杂度O(NlogN)的Timsort算法即可。
def triangles(A):
n = len(A)
result = 0
for x in range(n):
z = x + 2
for y in range(x + 1, n):
while (z < n and A[x] + A[y] > A[z]):
z += 1
result += z - y - 1
return result
def solution(A):
# 1. 排序不影响最后返回的result
# 2. 因为triangle部分计算复杂度是O(n^2),因此排序部分复杂度不超过O(n^2)即可
sort_A = sorted(A)
# 排序后的Array A的判断方法,就和本章PDF Material中的15.2完全一样了
result = triangles(sort_A)
return result
MinAbsSumOfTwo: Find the minimal absolute value of a sum of two elements.
题目概括:本题输入是一个有N个整数的Array A,每个正整数的取值范围为[-1,000,000,000..1,000,000,000],需要返回的,是A中任意两个元素a,b得到的abs(a+b)中最小的值,并且a,b可以选同一个值。
解法概括:这题除了先对Array A进行排序,再利用Caterpillar思想进行解之外,我没能想到可以不进行预排序的解法。不过这样整体的计算复杂度就是O(N * log(N)),从结果上看也是双百。
不过我没想清楚怎么说清楚,不对A进行排序是无法实现解答的:因为如果先对A进行复杂度为O(N)的min()和max()计算,在发现存在min(A)>=0或max(A)<=0时,其实可以直接在O(n)复杂度下给出最终答案。只有当不满足这两种情况下(也就是又有正数又有负数的情况下),才需要接下来在未排序的Array A中利用Caterpillar进行解答,而为什么不排序的情况下无法用Caterpillar给出O(n)复杂度的解答的原因,我有点没太想清楚。
不过Anyway,至少排序后如何利用Caterpillar获得答案还是很容易理解的,看下下边solution的大概代码也就明白了。因此,上述提到的不排序无法实现的证明,就看大家能不能给我些资料和思路了。
def solution(A):
# 因为A元素取值为[-10e,10e],初始化result为20e
begin, end, result = 0, len(A)-1, 2000000000
A.sort()
while begin <= end:
result = min(result, abs(A[begin] + A[end]))
if abs(A[begin]) > abs(A[end]):
begin += 1
else:
end -= 1
return result