一、最大公约数
自然数 a 和 b,如果 b 整除 a,则称 a 为 b 的倍数,b 为 a 的约数。几个自然数公有的约数,叫做这几个自然数的公约数。公约数中最大的一个,称为这几个自然数的最大公约数。
求两个正整数的最大公约数,即求不大于又时整除这两个数的最大自然数。
求取最大公约数常用四种算法:辗转相除法、穷举法、更相减损法、Stein 算法。
辗转相除法
欧几里得算法是用来求两个正整数最大公约数的算法。古希腊数学家欧几里得在其著作《The Elements》中最早描述了这种算法。
**定理:**两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数。
最大公约数 GCD(Greatest Common Divisor) 。
计算公式 gcd(a, b) = gcd(b, a mod b) (不妨设 a > b 且 r = a mod b, r 不为 0)。
证明:
a = kb + r(a,b,k,r 皆为正整数,假设 d 是 a, b 的一个公约数,a 和 b 都可以被 d 整除。
而 r = a - kb,两边同时除以 d,r/d = a/d - kb/d,由等式右边可知 m = r/d 为整数,因此 d 也是 b, a mod b 的公约数。
因 (a, b) 和 (b, a mod b) 的公约数相等,则其最大公约数也相等,得证。
# 最大公约数 辗转相除法(递归)
def gcd(a, b):
if a < b: a, b = b, a
while b:
a, b = b, a % b
return a
# 求两个数的最小公倍数 a * b = gcd(a, b) * lcm(a, b)
def lcm(a, b):
return a * b // gcd(a, b)
穷举法(也叫枚举法)
从两个数中较小数开始由大到小列举,直到找到公约数立即中断枚举,得到的公约数便是最大公约数 。
①定义1:对两个正整数 a, b 如果能在区间 [a,0] 或 [b,0] 内能找到一个整数 temp 能同时被 a 和 b 所整除,则 temp 即为最大公约数。
②定义2:对两个正整数 a, b, 如果若干个 a 之和或 b 之和能被 b 所整除或能被 a 所整除,则该和数即为所求的最小公倍数。
def gcd2(a, b):
tmp = min(a, b)
while tmp > 0:
if a % tmp == 0 and b % tmp == 0: break
tmp -= 1
return tmp
算法更相减损法
更相减损法(更相减损术),是出自《 九章算术》的一种求最大公约数的算法,它原本是为 约分而设计的,但它适用于任何需要求最大公约数的场合。
《九章算术》是中国古代的数学专著,其中的“更相减损术”可以用来求两个数的最大公约数,即“可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。
”翻译成现代语言如下:第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用 2 约简;若不是则执行第二步。第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。则第一步中约掉的若干个 2 与第二步中等数的乘积就是所求的最大公约数。其中所说的“等数”,就是最大公约数。求“等数”的办法是“更相减损”法。所以更相减损法也叫等值算法
def gcd3(a, b):
i = 0
while a % 2 == 0 and b % 2 == 0:
a //= 2
b //= 2
i += 1
while a != b:
if a > b: a = a - b
else: b = b - a
return a * 2**i
Stein 算法
一个奇数的所有约数都是奇数。
gcd(kx, ky) = k*gcd(x, y) 。
一奇一偶的情况: 设有 2x 和 y 两个数,其中 y 为奇数。因为 y 的所有约数都是奇数,所以 a = gcd(2x, y) 是奇数。a 是 x 的约数。gcd(2x, y) = gcd(x, y)。
两个奇数的情况:设有两个奇数 x 和 y,把 x 和 y 向偶数靠拢去化小。不妨设 x > y,x + y 和 x - y 是两个偶数,则有 gcd(x + y, x - y ) = 2 * gcd( (x + y) / 2, (x - y) / 2 ),设 m = (x + y) / 2 ,n = (x - y) / 2,则 m + n = x,m - n = y。设 a = gcd(m, n),则 m % a = 0, n % a = 0 ,所以 (m + n) % a = 0,(m - n) % a = 0 ,即 x % a = 0 ,y % a = 0 ,所以 a 是 x 和 y 的公约数,有 gcd(m, n) <= gcd(x, y)。再设 b = gcd(x, y)肯定为奇数,则 x % b = 0, y % b = 0,所以 (x + y) % b = 0,(x - y) % b = 0 ,又因为 x + y 和 x - y 都是偶数,跟前面一奇一偶时证明 a 是 x 的约数的方法相同,有 ((x + y) / 2) % b = 0, ((x - y) / 2) % b = 0 ,即 m % b = 0 ,n % b = 0 ,所以 b 是 m 和 n 的公约数,有 gcd(x, y) <= gcd(m, n)。所以 gcd(x, y) = gcd(m, n) = gcd((x + y) / 2, (x - y) / 2)。
对两个正整数 x > y:
1.均为偶数 gcd(x, y) = 2gcd(x/2, y/2);
2.均为奇数 gcd(x, y) = gcd( (x + y) / 2, (x - y) / 2 );
2.x 奇 y 偶 gcd(x, y) = gcd(x, y / 2);
3.x 偶 y 奇 gcd(x, y) = gcd(x / 2, y) 或 gcd(x, y) = gcd(y, x / 2);
# Stein 算法
def Stein(a, b):
factor = 0
if a < b:
a, b = b, a
if b == 0: return a # 0能被任何非 0 数整除
while a != b:
if a & 1:
if b & 1: # when a and b are both odd
b = (a - b) >> 1
a -= b
else: # when a is odd and b is even
b >>= 1
else:
if b & 1: # when a is even and b is odd
a >>= 1
if a < b:
a, b = b, a
else: # when a is odd and b is even
a >>= 1
b >>= 1
factor += 1
return a << factor
2447. 最大公因数等于 K 的子数组数目
class Solution:
def subarrayGCD(self, nums: List[int], k: int) -> int:
# def gcd(a, b):
# tmp = min(a, b)
# while tmp > 0:
# if a % tmp == 0 and b % tmp == 0: return tmp
# tmp -= 1
left, ans = -1, 0
for i, x in enumerate(nums):
if x % k == 0:
while i > left and gcd(x, nums[i]) != k:
i -= 1
ans += i - left
else: left = i
return ans
6234. 最小公倍数为 K 的子数组数目
class Solution:
def subarrayLCM(self, nums: List[int], k: int) -> int:
# def lcm(x, y): # 穷举法
# greater = x if x > y else y # 获取最大的数
# while(True):
# if((greater % x == 0) and (greater % y == 0)):
# return greater
# greater += 1
# return x * y
n = len(nums)
left, ans = -1, 0
for i, x in enumerate(nums):
if k % x == 0:
while i > left and lcm(x, nums[i]) != k:
i -= 1
ans += i - left
else: left = i
return ans
二、最小公倍数
算法:最小公倍数 = 两数相乘 ÷ 两数的最大公约数
1、穷举法
从大的那个数(理论上,实际可以从任意一个数或者从正整数开始)开始一个个验证是否可以同时整除 a 和 b,如果找到则跳出循环,没找到则加一继续找。
def lcm(x, y): # 很慢
greater = max(x,y)
while greater % x or greater % y:
greater += 1
return greater
2、两数之积除以两数的最大公约数
print('%d'%(a*b/[x for x in range(1,a+1) if a%x==0 and b%x ==0][-1]))
上面代码的函数写法
def gcd(a,b):
if a < b: a, b = b, a
remainder = a % b
if remainder == 0: return b
else: return gcd(b, remainder)
def lcm(a,b):
return a * b // gcd(a, b)
lcm(a,b)
3、找两数差距法
ma = max(a, b)
mi = min(a, b)
for i in range(1, mi+1):
if ma * i % mi == 0:
print(ma*i)
break
4、math
import math
print(math.gcd(a, b)) # 利用函数求解最大公约数
print(a * b // math.gcd(a, b)) # 利用上面的函数求解最小公倍数