Codility刷题之旅 - Caterpillar method

569 阅读3分钟

今天继续Codility的Lessons部分的第15个主题——Caterpillar method

Codility - Lessons - Caterpillar method

还是按先总结红色部分PDF,然后再完成蓝色的Tasks部分的节奏来~

image.png

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.

image.png 题目概括:本题输入是一个递增的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

image.png

CountDistinctSlices: Count the number of distinct slices (containing only unique numbers).

image.png 题目概括:本题输入是一个正整数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 

image.png

CountTriangles: Count the number of triangles that can be built from a given set of edges.

image.png 题目概括:输入是一个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

image.png

MinAbsSumOfTwo: Find the minimal absolute value of a sum of two elements.

image.png 题目概括:本题输入是一个有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

image.png