乐趣区

关于算法:我是如何用最大公约数秒杀算法题的

对于最大公约数有专门的钻研。而在 LeetCode 中尽管没有间接让你求解最大公约数的题目。然而却有一些间接须要你求解最大公约数的题目。

比方:

  • 914. 卡牌分组
  • 365. 水壶问题
  • 1071. 字符串的最大公因子

因而如何求解最大公约数就显得重要了。

如何求最大公约数?

定义法

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

复杂度剖析

  • 工夫复杂度:最好的状况是执行一次循环体,最坏的状况是循环到 smaller 为 1,因而总的工夫复杂度为 $O(N)$,其中 N 为 a 和 b 中较小的数。
  • 空间复杂度:$O(1)$。

辗转相除法

如果咱们须要计算 a 和 b 的最大公约数,使用辗转相除法的话。首先,咱们先计算出 a 除以 b 的余数 c,把问题转化成求出 b 和 c 的最大公约数;而后计算出 b 除以 c 的余数 d,把问题转化成求出 c 和 d 的最大公约数;再而后计算出 c 除以 d 的余数 e,把问题转化成求出 d 和 e 的最大公约数。….. 以此类推,逐步把两个较大整数之间的运算转化为两个较小整数之间的运算,直到两个数能够整除为止。

def GCD(a: int, b: int) -> int:
    return a if b == 0 else GCD(b, a % b)

复杂度剖析

  • 工夫复杂度:$O(log(max(a, b)))$
  • 空间复杂度:空间复杂度取决于递归的深度,因而空间复杂度为 $O(log(max(a, b)))$

更相减损术

辗转相除法如果 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)))。这个时候咱们能够将 辗转相除法 更相减损术 做一个联合,从而在各种状况都能够取得较好的性能。

形象化解释

上面咱们对下面的过程进行一个表形象地解说,实际上这也是教材外面的解说形式,我只是照搬过去,减少一下本人的了解罢了。咱们来通过一个例子来解说:

如果咱们有一块 1680 米 * 640 米 的土地,咱们心愿讲起分成若干正方形的土地,且咱们想让正方形土地的边长尽可能大,咱们应该如何设计算法呢?

实际上这正是一个最大公约数的利用场景,咱们的指标就是求解 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$。

为了解决这个问题,咱们能够通过集合论的常识。

一点点汇合常识:

  • 如果把有序序列中小于等于 x 的能够被 x 整除,且是 a 的倍数的值形成的汇合为 SA,汇合大小为 A
  • 如果把有序序列中小于等于 x 的能够被 x 整除,且是 b 的倍数的值形成的汇合为 SB,汇合大小为 B
  • 如果把有序序列中小于等于 x 的能够被 x 整除,且是 c 的倍数的值形成的汇合为 SC,汇合大小为 C

那么最终的答案就是 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)

接下来就是二分套路了,二分局部看不懂的倡议看下我的二分专题。

代码(Python3)

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

复杂度剖析

  • 工夫复杂度:$logn$。
  • 空间复杂度:gcd 和 lcm 的递归树深度,根本可忽略不计。

总结

通过这篇文章,咱们不仅明确了最大公约数的 概念以及求法 。也形象化地感知到了最大公约数计算的 原理 。最大公约数和最小公倍数是两个类似的概念,对于最大公约数和最小公倍数的题目在力扣中不算少,大家能够通过 数学标签 找到这些题。更多对于算法中的数学知识,能够参考这篇文章刷算法题必备的数学考点汇总

这篇文章的第二篇也马上要公布了。

以上就是本文的全部内容了。大家对此有何认识,欢送给我留言,我有工夫都会一一查看答复。更多算法套路能够拜访我的 LeetCode 题解仓库:https://github.com/azl3979858…。目前曾经 40K star 啦。大家也能够关注我的公众号《力扣加加》带你啃下算法这块硬骨头。

退出移动版