Codility刷题之旅 - Euclidean algorithm

298 阅读3分钟

今天继续Codility的Lessons部分的第12个主题——Euclidean algorithm

Codility - Lessons - Euclidean algorithm

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

image.png

PDF Reading Material

  • 前言:介绍“辗转相除法”(又称欧几里得算法)是计算两个正整数的greatest common divisor (gcd),也就是最大公因数的有效方法。
  • 12.1 Euclidean algorithm by subtraction: 介绍辗转相除法的最初版本,将输入a与b不断进行相减,直到差值为0,返回此时的a/b。
  • 12.2 Euclidean algorithm by division: 介绍辗转相除法的除法版本,对于输入a与b(a>=b),如果a%b!=0,则赋值a=b, b=a%b进入嵌套迭代,直到满足a%b==0后返回b。然后证明了该算法的正确性,以及时间复杂度 O(log(a + b))。
  • 12.3 Binary Euclidean algorithm: 又介绍了一种二进制欧几里得算法。跟12.2的算法比,这种算法在计算比较大的正整数输入时,计算时长上是比较稳定的,因为不需要做a%b这样十进制相除取余的操作,只需要与2进行相除进行取余或取整操作。
  • 12.4 Least common multiple: 介绍了最小公倍数(lcm)的概念,因为lcm其实就等于a*b/gcd(a,b),所以同样可以在O(log(a + b))的复杂度下实现计算。
  • 12.5 Exercise: 例子是三个人拥有面值分别为a,b,c的硬币,需要求三个人硬币面值的最小公约数。这里用了迭代的思想,也就是lcm(a,b,c)=lcm(a, lcm(b,c)),也就是求两次lcm即可以求出结果。

Tasks

image.png

本题输入是N和M,M是从位置0开始吃蛋糕的步长,N是蛋糕的总个数。也就是说,第二步吃到的蛋糕,就是第M%N个蛋糕。需要返回的值,是按照上述逻辑吃,第一次吃到已吃过的蛋糕时的步数。

其实比较容易想到本题的返回值其实就是N/gcd(N,M),因为最终的步数k需要满足kM=nN,因此k=nN/M。而确保k是整数的n的最小值,可以想到就是M/gcd(N,M),带入发现k=N/gcd(N,M)。这里用PDF中给出的欧几里得算法的除法版本进行计算获得gcd,代码如下:

def get_gcd(a,b):
    if a%b==0: return b 
    else: return get_gcd(b, a%b)

def solution(N, M):
    # 1. 找到最大公因数
    gcd = get_gcd(N, M) 
    # 2. N//gcd
    return N//gcd

image.png

image.png

本题题意还是比较容易理解的,输入是A,B两个等长Array,然后对于A、B中每组同index的a、b,需要判断a,b的质数因子是否完全相同。

解题思路要利用gcd,因为计算出a、b的最大公因数(gcd)后,我们可以知道a//gcd和b//gcd之间一定是不存在大于1的公因数的,所以如果这两个结果里存在大于1的值的话,一定是要和已经算好的gcd的prime divisor完全重合,不能存在自己专属的prime divisor。

这个过程可以通过迭代计算get_gcd(a, gcd)和赋值a=a/get_gcd(a,gcd)实现,并且设定好get_gcd(a,gcd)=1时的提前跳出逻辑。最终得跳出循环后,处理得到的a,b都是1,则说明a,b都不存在gcd的prime divisor之外的其他prime_divisor,可以正常计1,反之则按题意是记0,最终返回计数之和:

def get_gcd(a,b):
    if a%b==0: return b 
    else: return get_gcd(b, a%b)

def solution(A, B):
    res = [0] * len(A)
    for i,(a,b) in enumerate(zip(A,B)):
        gcd = get_gcd(a, b)
        # a/gcd和b/gcd,必须和gcd有同样的公约数
        while a>1:
            gcd_a = get_gcd(a, gcd)
            a /= gcd_a
            if gcd_a==1:
                break 
        if a>1:
            continue
        while b>1:
            gcd_b = get_gcd(b, gcd)
            b /= gcd_b 
            if gcd_b==1:
                break 
        if b==1:
            res[i]=1
    return sum(res)

image.png