关于最大公约数有专门的研究。 而在 LeetCode 中虽然没有直接让你求解最大公约数的题目。可是却有一些间接须要你求解最大公约数的题目。python
好比:git
所以如何求解最大公约数就显得重要了。github
def GCD(a: int, b: int) -> int: smaller = min(a, b) while smaller: if a % smaller == 0 and b % smaller == 0: return smaller smaller -= 1
复杂度分析算法
若是咱们须要计算 a 和 b 的最大公约数,运用展转相除法的话。首先,咱们先计算出 a 除以 b 的余数 c,把问题转化成求出 b 和 c 的最大公约数;而后计算出 b 除以 c 的余数 d,把问题转化成求出 c 和 d 的最大公约数;再而后计算出 c 除以 d 的余数 e,把问题转化成求出 d 和 e 的最大公约数。..... 以此类推,逐渐把两个较大整数之间的运算转化为两个较小整数之间的运算,直到两个数能够整除为止。api
def GCD(a: int, b: int) -> int: return a if b == 0 else GCD(b, a % b)
复杂度分析app
展转相除法若是 a 和 b 都很大的时候,a % b 性能会较低。在中国,《九章算术》中提到了一种相似展转相减法的 更相减损术。它的原理是:两个正整数 a 和 b(a>b),它们的最大公约数等于 a-b 的差值 c 和较小数 b 的最大公约数。
。性能
def GCD(a: int, b: int) -> int: if a == b: return a if a < b: return GCD(b - a, a) return GCD(a - b, b)
上面的代码会报栈溢出。缘由在于若是 a 和 b 相差比较大的话,递归次数会明显增长,要比展转相除法递归深度增长不少,最坏时间复杂度为 O(max(a, b)))。这个时候咱们能够将展转相除法
和更相减损术
作一个结合,从而在各类状况均可以得到较好的性能。网站
下面咱们对上面的过程进行一个表形象地讲解,实际上这也是教材里面的讲解方式,我只是照搬过来,增长一下本身的理解罢了。咱们来经过一个例子来说解:ui
假如咱们有一块 1680 米 * 640 米 的土地,咱们但愿讲起分红若干正方形的土地,且咱们想让正方形土地的边长尽量大,咱们应该如何设计算法呢?spa
实际上这正是一个最大公约数的应用场景,咱们的目标就是求解 1680 和 640 的最大公约数。
将 1680 米 * 640 米 的土地分割,至关于对将 400 米 * 640 米 的土地进行分割。 为何呢? 假如 400 米 * 640 米分割的正方形边长为 x,那么有 640 % x == 0,那么确定也知足剩下的两块 640 米 * 640 米的。
咱们不断进行上面的分割:
直到边长为 80,没有必要进行下去了。
给你三个数字 a,b,c,你须要找到第 n 个(n 从 0 开始)有序序列的值,这个有序序列是由 a,b,c 的整数倍构成的。 好比: n = 8 a = 2 b = 5 c = 7 因为 2,5,7 构成的整数倍构成的有序序列为 [1, 2, 4, 5, 6, 7, 8, 10, 12, ...],所以咱们须要返回 12。 注意:咱们约定,有序序列的第一个永远是 1。
你们能够经过 这个网站 在线验证。
一个简单的思路是使用堆来作,惟一须要注意的是去重,咱们可使用一个哈希表来记录出现过的数字,以达到去重的目的。
代码:
ss Solution: def solve(self, n, a, b, c): seen = set() h = [(a, a, 1), (b, b, 1), (c, c, 1)] heapq.heapify(h) while True: cur, base, times = heapq.heappop(h) if cur not in seen: n -= 1 seen.add(cur) if n == 0: return cur heapq.heappush(h, (base * (times + 1), base, times + 1))
对于此解法不理解的可先看下我以前写的 几乎刷完了力扣全部的堆题,我发现了这些东西。。。(第二弹)
然而这种作法时间复杂度过高,有没有更好的作法呢?
实际上,咱们可对搜索空间进行二分。首先思考一个问题,若是给定一个数字 x,那么有序序列中小于等于 x 的值有几个。
答案是 x // a + x // b + x // c 吗?
// 是地板除
惋惜不是的。好比 a = 2, b = 4, n = 4,答案显然不是 4 // 2 + 4 // 4 = 3,而是 2。这里出错的缘由在于 4 被计算了两次,一次是 $2 * 2 = 4$,另外一次是 $4 * 1 = 4$。
为了解决这个问题,咱们能够经过集合论的知识。
一点点集合知识:
那么最终的答案就是 SA ,SB,SC 构成的大的集合(须要去重)的中的数字的个数,也就是:
$$ A + B + C - sizeof(SA \cap SB) - sizeof(SB \cap SC) - sizeof(SA \cap SC) + sizeof(SA \cap SB \cap SC) $$
问题转化为 A 和 B 集合交集的个数如何求?
A 和 B,B 和 C, A 和 C ,甚至是 A,B,C 的交集求法都是同样的。
实际上, SA 和 SB 的交集个数就是 x // lcm(A, B),其中 lcm 为 A 和 B 的最小公倍数。而最小公倍数则能够经过最大公约数计算出来:
def lcm(x, y): return x * y // gcd(x, y)
接下来就是二分套路了,二分部分看不懂的建议看下个人二分专题。
class Solution: def solve(self, n, a, b, c): def gcd(x, y): if y == 0: return x return gcd(y, x % y) def lcm(x, y): return x * y // gcd(x, y) def possible(mid): return (mid // a + mid // b + mid // c - mid // lcm(a, b) - mid // lcm(b, c) - mid // lcm(a, c) + mid // lcm(a, lcm(b, c))) >= n l, r = 1, n * max(a, b, c) while l <= r: mid = (l + r) // 2 if possible(mid): r = mid - 1 else: l = mid + 1 return l
复杂度分析
经过这篇文章,咱们不只明白了最大公约数的概念以及求法。也形象化地感知到了最大公约数计算的原理。最大公约数和最小公倍数是两个类似的概念, 关于最大公约数和最小公倍数的题目在力扣中不算少,你们能够经过数学标签找到这些题。更多关于算法中的数学知识,能够参考这篇文章刷算法题必备的数学考点汇总
这篇文章的第二篇也立刻要发布了。
以上就是本文的所有内容了。你们对此有何见解,欢迎给我留言,我有时间都会一一查看回答。更多算法套路能够访问个人 LeetCode 题解仓库:https://github.com/azl3979858... 。 目前已经 40K star 啦。你们也能够关注个人公众号《力扣加加》带你啃下算法这块硬骨头。