Codility刷题之旅 - Time Complexity

323 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情

今天继续Codility的Lessons部分的第三个主题——Time Complexity,可以预感到Efficiency应该是这个部分题目的考察重点。

Codility - Lessons - Time Complexity

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

image.png

PDF Reading Material

PDF大概可以分成下边这些部分:

  • 前言:
  • Comparison of different complexities:
  • Time limit:
  • Space Complexities:
  • Exercise:

Task

Task1: FrogImp

image.png

题目很简单,输入为X,Y,D,其中X<=Y。需要返回一只青蛙从X开始,按D的步长进行跳跃,至少跳跃多少次之后可以到达大于等于Y。

第一次没有关注efficiency,也没怎么多想,直接写了一个初始化步数为0,之后while循环累加直到跳出返回的逻辑:

def solution(X, Y, D):
    # Implement your solution here
    jumps = 0
    while X<Y:
        X += D
        jumps += 1    
    return jumps

结果Correctness倒是100%了,但是Peformance直接来了个0%:

image.png

重新修改了下计算方式,实际上可以直接不走时间复杂度为n的循环,而是直接将X和Y做差,与步长D进行计算得到结果:

def solution(X, Y, D): 
    # Implement your solution here 
    diff = Y-X 
    # 直接根据Y和X的差值与D相除后的整数和余数,确定需要jump的次数
    a, b = diff//D, diff%D 
    return a + (b>0)

image.png

Task2: PermMissingElem

image.png

题目概括如下:输入为有N个元素的Array A,其中的元素为从1到N+1各不相同的整数,也就是说正好缺失了从1~N+1的N+1个整数中的一个,需要的输出即为缺失的这个数。

因为还是重点考察运行效率,所以选择循环两次的复杂度为n的解法:

  1. 构建一个全是0的list
  2. 然后循环A中元素一次,并用1中的list的各个index处的值进行计数。
  3. 再循环一次list中每个index的具体计数值,在第一个计数值为0处,直接跳出返回当时的index+1
def solution(A):
    # 建立一个全是0的list,来记录每个自然数出现的次数
    count_lst = [0] * (len(A)+1)
    for a in A:
        count_lst[a-1]+=1
    
    # 再重新扫一遍count_lst,发现次数为0的,直接返回当时的index+1
    for i,v in enumerate(count_lst):
        if v==0:
            return i + 1

image.png

Tasks3: TapeEquilibrium

image.png

概括题目如下:输入为Array A,可以任意选择Array中的某个index处截断,得到至少包含一个元素的前部分Array(称为A1),和至少包含一个元素的后部分Array(称为A2),需要的输出,是所有的截断可能下,最小的|sum(A1)-sum(A2)|

解题思路如下:Array A里的元素可能为正,可能为负。所以其实我们需要算出每一个截断位置的差值绝对值结果,才能最终确定最小差值绝对值是多少。

因为sum(A1)和sum(A2)两部分,其实对于不同位置的截断,是存在一个累加关系的,所以我们可以选择在A中从左到右+从右到左分别各扫一次,就可以依次算出每个截断位置处的sum(A1)和sum(A2)的值,当然空间上就需要构造一个长度为len(A)-1的数组来保存结果,我们称这个数组为diff_arr。

在来回扫完2遍,并分别把截断位置在不同index处的sum(A1)/sum(A2),加到/减到diff_arr的对应index处之后,我们便得到所有截断位置为index处时的sum(A1)-sum(A2)结果。此时再在diff_arr里从左到右扫一遍,就可以得到diff_arr中最小的绝对值了:

def solution(A):
    # Implement your solution here
    len_A = len(A)
    
    diff_arr = [0] * (len_A-1)
 
    # 从左到右计算当P在index处时的sum(A1), 并加在diff_arr[index]处
    a1_sum = 0
    for i, a in enumerate(A[:-1]):
        a1_sum += a
        diff_arr[i] += a1_sum 
    
    # 从右到左计算当P在index处时的sum(A2),并减在diff_arr[index处]
    a2_sum = 0 
    for i, b in enumerate(A[::-1][:-1]):
        a2_sum += b 
        diff_arr[len_A-2-i] -= a2_sum

    # 再从左到右扫一遍算好的diff_arr,找到最低的abs值
    min_abs_diff = abs(diff_arr[0])
    for d in diff_arr[1:]:
        if d == 0: # 最小的abs就是0,所以看到0返回就好
            return 0 
        if d<0:
            if -d<min_abs_diff:
                min_abs_diff = -d
        else: # d>0
            if d<min_abs_diff:
                min_abs_diff = d
    return min_abs_diff

image.png