今天继续Codility的Lessons部分的第12个主题——Euclidean algorithm
Codility - Lessons - Euclidean algorithm
还是按先总结红色部分PDF,然后再完成蓝色的Tasks部分的节奏来~
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
本题输入是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
本题题意还是比较容易理解的,输入是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)