我是如何用「最大公約數」秒殺算法題的

關於最大公約數有專門的研究。 而在 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

複雜度分析算法

  • 時間複雜度:最好的狀況是執行一次循環體,最壞的狀況是循環到 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 的最大公約數。..... 以此類推,逐漸把兩個較大整數之間的運算轉化爲兩個較小整數之間的運算,直到兩個數能夠整除爲止。api

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

複雜度分析app

  • 時間複雜度:$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)))。這個時候咱們能夠將展轉相除法更相減損術作一個結合,從而在各類狀況均可以得到較好的性能。網站

形象化解釋

下面咱們對上面的過程進行一個表形象地講解,實際上這也是教材裏面的講解方式,我只是照搬過來,增長一下本身的理解罷了。咱們來經過一個例子來說解: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$。

爲了解決這個問題,咱們能夠經過集合論的知識。

一點點集合知識:

  • 若是把有序序列中小於等於 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 啦。你們也能夠關注個人公衆號《力扣加加》帶你啃下算法這塊硬骨頭。

相關文章
相關標籤/搜索