如何利用扩展欧几里得算法求解不定方程_客户端不用的算法系列:从头条笔试题认识扩展欧几里得算法...
難度較高,閱讀時間大概 28 分鐘
這是數論的第二篇,在《素數篩法》中,我們重溫了素數這個數學定義,并且給出了區別于教科書上更高效的 Eratosthenes 篩法和歐拉線性篩。這篇文會從 GCD 問題出發,一起來探究一下擴展歐幾里得算法。
看了標題,也許你有疑問,什么是歐幾里得算法?歐幾里得算法是為了解決 GCD 問題,這里的 GCD 是指 Greatest Common Divisor 即?最大公約數,而不是 iOS 中的 Grand Central Dispatch ? 。所以這篇分享是關于算法的。
歐幾里得算法(GCD)
求 GCD 在數論中公認的最常用算法即為歐幾里得算法,也就是我們在高中時學到的輾轉相除法。
歐幾里得算法的基本原理用一句話就可以說清楚:兩個整數的最大公約數等于其中較小的數和兩數的差的最大公約數,即 gcd(a, b) = gcd(b, a mod b)。
為什么可以這么求呢,這里可以簡單證明一下:
假設 a, b (a > b) 兩個數的一個公約數是 t ,則有
因為 a > b ,設 a = k × b + r ,即 r = a mod b ,將 a、b 代入展開可得:
由于 (n - k × m) × t 一定是整數,所以 a、b 的公約數 t 也是 r 的約數。所以如果我們遞歸的求解 a mod b 也就是 a % b ,就可以得到 a、b 的最大公約數 GCD 了。什么時候遞歸結束呢?當 a % b == 0 的時候,因為在這個過程中,如果 a mod b 無法求得正整數 r 時,則無法繼續按照上述規律繼續拆分。
# pythondef gcd(a, b): return a if b == 0 else gcd(b, a % b)// Cint gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b);}這里另外提一句,a、b 兩數的最大公倍數 LCM(a, b) = a * b / GCD(a, b) 。這里就不證明了,有興趣的自己谷歌。
一道頭條的筆試題
上個月在脈脈上看到一道頭條校招的筆試題,看評論說是“地獄難度”的,我們通過這道題來延伸說一下。先來看下這題的題面:
有一臺用電容組成的計算器,其中每個電容組件都有一個最大容量值(正整數)。對于單個電容,有如下操作指令:
指令1:放電操作-把該電容當前電量值清零;
指令2:充電操作-把該電容當前電量補充到最大容量值;
指令3:轉移操作-從電容 A 中盡可能多的將電量轉移到電容 B ,轉移不會有電量損失,如果能夠充滿 B 的最大容量,那剩余的電量仍然會留在 A 中。
現在已知有兩個電容,其最大容量分別為 a 和 b,其初始狀態都是電量值為 0,希望通過一些列的操作可以使其中某個電容(無所謂哪一個)中的電量值等于 c (c也是正整數),這一些列操作所用的最少指令條數記為 M,如果無論如何操作,都不可能完成,則定義此時 M = 0。
顯然對于每一組確定的 a,b,c,一定會有一個 M 與之對應。
這里需要輸入的是 a、b、c ,給出兩個樣例,例如 a = 3, b = 4, c = 2 ,則最少需要 4 個指令完成。
解釋:設最大容量為 3 的是 A 號電容,另一個是 B 號電容,對應的操作是 (充電 A)=> (轉移 A -> B) => (充電 A)=> (轉移 A -> B) ,這樣 A 就是目標的 2 電量。
第二個樣例 a = 2, b = 3, c = 4,由于 a 和 b 都無法到目標電量 4,所以輸出 0 代表無解。
這道題我們拿到以后,第一反應就是模擬三個指令,然后使用 BFS 廣度優先搜索來搜出答案,只要任意情況到達目標的 c 值就停下來。但是題目中給出了數據量 0 < a, b, c < 10^9 ,這個數據量約束了我們無法使用暴力搜索來求解。
簡要分析
首先從筆試的角度來分析,由于筆試時會有數據范圍的測試,這道題給出的數據范圍大概是這樣:
0?c?10^0?c?10^0?c?10^所以如果沒有任何的思路和數論基礎,我建議使用 BFS 直接寫一版暴力,最少可以通過 > 50% 的數據,從而拿到一定的分數。(其實這就是 OI 得分賽制,沒有思路先暴力搶分)。
下面我們來分情況討論這個問題:
情況一
樣例已經給出了一種邊界情況,即當 c > max(a, b) ,這種情況是無法使得 A 和 B 的電量達到 c 的。直接輸出 0。
情況二
還有一種我們可以直接想到的情況,當 a = c 或者 b = c 的時候,只進行一次充電操作就可以完成,直接輸出 1。
情況三
接下來我們考慮一般情況,即需要滿足以下前提條件:
我們將這個問題換一個思路轉化一下假設給出的 a 、b、 c 一定有解,那么我們來設置對 A 做了 x 次的充(放)電,對 B 做了 y 次的充(放)電,并且做了 k 次的操作三。如果將 A、B 當做一個大電容來看這個電容只有充放電 a 單位、充放電 b 單位這 4 種操作。那么我們就可以列出一個關系式:
由于 a、b 為非負整數,又因為前提條件 c < max(a, b) ,則 x 和 y 符號相反。
暫且,我們先不管做了幾次操作三,先只考慮充放電問題,那其實就是已知 a、b、c,我們在給定范圍內求解 x 和 y 的解就可以了。那么這個問題我們要如何求解呢?這就是擴展歐幾里得算法所要解決的問題。
擴展歐幾里得算法(Extended Euclidean)
帶 * 的章節略有難度。如果是從解決問題的工程角度出發,可以跳過證明直接記結論。
在推導上述問題的求解算法之前,我們需要先了解以下幾個概念知識。
丟番圖方程(Diophantine Equation)
丟番圖方程指的是:未知數個數多于方程個數,且未知數只能是整數的整數系數方程或方程組。例如以下式中,a、b、c 都為整數:
關于代數學鼻祖丟番圖(Diophantus)除了有《算數》這本開山巨作之外,還有一個好玩的數學題目墓志銘,有興趣可以自己了解。
裴蜀定理(Bézout's identity)
在數論中,裴蜀定理是一個關于最大公約數的定理。這個定理說明了對于任意整數 a、b 和他們的最大公約數 d,關于未知數 x 和 y 的線性丟番圖方程:
有解,當且僅當 m 是 d 的倍數時。這個等式也被稱為裴蜀等式。
裴蜀等式有解時必然有無窮多個整數解,每組解 x 、y 都稱之為裴蜀數,可用輾轉相除法求得。
輾轉相除法實現擴展歐幾里得算法
既然說可以用輾轉相除法來解決這個問題,那么我們先來說明一下如何通過輾轉相除法來求二元一次線性丟番圖方程。
輾轉相除法過程
以 23x + 17y = 1 為例,我們來求 GCD(23, 17):
改寫成余數形式
將等式右邊的第一項移項:
反向帶入原式
帶下劃線的 6 和 5 會使用 (1) 和 (2) 兩個式子反向帶入,形同換元:
所以反解得,x = 3, y = -4 是上述二元一次線性丟番圖方程的一組解。
* 擴展歐幾里得算法證明
來觀察一下輾轉相除法的最后兩個式子,終止條件是:
當且僅當第二個式子為 0 的時候停止這個遞歸運算。如何延伸到一般情況呢?我們將待求變量設為字母來嘗試一下。假設此時,我們要求 an 和 bn 為系數的二元一次線性丟番圖方程的系數,即待求方程:
根據上述的改寫余數形式,我們可以列出式一(| 是整除的意思):
假設未到達最終的終止條件,則有:
第二個式子中我們可以發現:
同理,第 n 個式子中有:
根據輾轉相除的規則,我們知道第 0 項中 b = 0、 a = 1 ,而我們要求的是第 n 項中的 a 和 b,所以可以通過 a 和 b 的遞推公式逐一推導而來。
如此我們證明了 an 和 bn 的遞推關系,下面我們來證明 xn 的遞推關系。
由上文證得了:
我們將其帶入到第一個式子中:
所以可以求得:
由于輾轉相除的推論我們可得:
所以:
即:
代碼實現擴展歐幾里得算法
為了實現上述的反向帶入原式的過程,我們通過遞歸遞歸到最深的一層,將每一層的解帶入即可完成最終的求解:
# pythondef ex_gcd(a, b): if b == 0: return 1, 0, a else: x, y, r = ex_gcd(b, a % b) x, y = y, (x - (a // b) * y) return x, y, r// c++int ex_gcd(int a, int b, int &x, int &y) { if(b == 0) { x = 1; y = 0; return a; } int r = ex_gcd(b, a % b, x, y); int t = y; y = x - (a / b) * y; x = t; return r;}但是我們注意到,由于裴蜀定理,我們求解的丟番圖方程中,等號右邊的常數必須是 k * gcd(a, b)。所以我們的求解其實是:
所以通過擴展 GCD 算法求得的 x0 和 y0 這組解,并不是我們要求的最終解。同樣的,我們對其擴大 k 倍就是我們想要對結果:
小結
有了這些知識,你對那道“地獄難度”的頭條面試題有沒有更多的想法呢?這里有一道 [LeetCode-365] 水壺問題 你可以嘗試一下,做完之后想必會對擴展 GCD 算法有更深的理解。
至于頭條面試題,我將在下一篇文繼續講述并代碼實現此題的解法。
總結
以上是生活随笔為你收集整理的如何利用扩展欧几里得算法求解不定方程_客户端不用的算法系列:从头条笔试题认识扩展欧几里得算法...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jenkins查询mysql_jenki
- 下一篇: 单片机烧录软件编写_单片机技术系列之一: