關於最大公約數有專門的研究。 而在 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 啦。你們也能夠關注個人公衆號《力扣加加》帶你啃下算法這塊硬骨頭。