【面试相关】python实现快速幂取余算法详解
假設我們要計算 2102^{10}210 對1000取模的結果,可以很簡單的得到24。但是如果要求 210002^{1000}21000 對1000取模的結果,常規方法就行不通了,因為常規的變量無法容納這么大的數值。為此,需要借助數學技巧求解。
循環求余法
首先引入一個好理解的取模運算公式:
(a×b)%p=(a%p×b%p)%p(a \times b) \% p = (a \% p \times b \% p) \% p (a×b)%p=(a%p×b%p)%p
舉個例子:
a=9,b=9(9×9)%6=(9%6×9%6)%6=(3×3)%6=3a=9, b=9\\ (9 \times 9) \% 6 = (9\%6 \times 9\%6)\%6 = (3 \times 3) \% 6 = 3 a=9,b=9(9×9)%6=(9%6×9%6)%6=(3×3)%6=3
受上面計算過程的啟發,不難得到下面的取模運算公式:
xa%p=((x%p)a)%px^a \% p = \left( (x \% p)^a \right) \% p xa%p=((x%p)a)%p
即 a 個 x 的乘積可以分解為 a 個 x 對 p 余數的乘積再對 p 取余,即 ((x%p)a)%p\left( (x \% p)^a \right) \% p((x%p)a)%p 。
舉個例子:
x=9,a=2,p=6(92)%6=(9%6)2%6=32%6=3x=9, a=2, p=6\\ (9^2) \% 6 =(9\%6 )^2\%6 = 3 ^2 \% 6 = 3 x=9,a=2,p=6(92)%6=(9%6)2%6=32%6=3
對于上面的例子,如果 a = 1000,難道最后還得計算 31000%63^{1000} \% 631000%6 嗎?其實可以這么做:
在進行 a 次迭代計算 x 對 p 余數的過程中,先將第 iii 次迭代得到的余數用變量 rem 保存起來,在進行第 i+1i + 1i+1 次迭代的時候,由 (rem * (x%p)) % p 所得到的結果就是 xi+1%px^{i+1} \%pxi+1%p 的結果。
舉個例子,求 x3%px^3 \% px3%p 的過程如下:
1,rem = x % p 2,rem = x^2 % p = ((x%p) * (x%p)) % p = (rem * (x%p)) % p 3,rem = x^3 % p = ((x^2%p) * (x%p)) % p = (rem * (x%p)) % p可以看到這個過程的時間復雜度是 O(N),其中 N = a,代碼實現如下:
# 求 (x^a) % p —— 循環求余法 def reminder1(x, a, p):rem = 1for _ in range(a):rem = (rem * (x%p)) % preturn remx, a, p = 2,100000000, 3 print(reminder1(x, a, p)) # 用時:9.06s觀察(rem * (x%p)) % p,發現既然括號的外面要用 p 取模,那么內部的(x%p)就顯得沒有必要了,因此可以簡化為:(rem * x) % p :
# 求 (x^a) % p —— 循環求余法 def remainder2(x, a, p):rem = 1for _ in range(a):rem = (rem * x) % preturn remx, a, p = 2,100000000, 3 print(reminder2(x, a, p)) # 用時:6.76s快速冪求余
上面的過程中,我們每一次迭代都是計算 x%px\% px%p,所以需要迭代 a 次。其實這個過程還有優化的空間:
如果我們每次都計算 x2%px^2 \% px2%p,那只需要迭代 a/2 次。也就是說,可以把指數 a 級的問題轉換為 a/2 級的問題,如果繼續分解這個計算過程,又可以得到:
xa%p=(x2%p)a/2%p=((x2%p)2%p)a/4%p=...x^a \% p = (x^2 \% p)^{a/2} \% p = \left( (x^2 \% p)^2\%p \right)^{a/4} \% p = ... xa%p=(x2%p)a/2%p=((x2%p)2%p)a/4%p=...
最終計算 xa%px^a \%pxa%p 的時間復雜度僅為 O(logN)O(logN)O(logN) 。
實際上,上面的式子忽略了一個問題, a 可能是奇數也可能是偶數,因此對 a/2 的處理上需要遵循下面的公式:
xa%p={(x2%p)a//2%p,a為偶數[(x%p)(xa?1%p)]%p=[x(x2%p)a//2]%p,a為奇數x^{a} \% p=\left\{\begin{array}{l} \left(x^{2} \% p\right)^{a / / 2} \% p \qquad \qquad \qquad \qquad \qquad \qquad \qquad, a 為偶數\\ {\left[(x \% p)\left(x^{a-1} \% p\right)\right] \% p=\left[x\left(x^{2} \% p\right)^{a / / 2}\right] \% p} \quad\; ,a為奇數 \end{array}\right. xa%p={(x2%p)a//2%p,a為偶數[(x%p)(xa?1%p)]%p=[x(x2%p)a//2]%p,a為奇數?
式子中的 // 表示除法運算的結果向下取整。此計算過程的代碼實現如下:
為什么 rem 只在 a 為奇數的時候才計算呢?
假設 a = 4,那么
- 第一次迭代后 a=2, x = x^2 % p;
- 第二次迭代后 a=1,x = (x^2 % p)^2 %p;
- 第三次迭代時,可以求得 rem = (1 * x) % p,且 a = 0;
- 結束循環,返回了 rem。
假設 a = 5,那么:
- 第一次迭代后 rem = x%p,a=2, x = x^2 % p;
- 第二次迭代后 a=1,x = (x^2 % p)^2 %p;
- 第三次迭代時,可以求得 rem = (rem * x) % p,且 a = 0;
- 結束循環,返回了 rem。
也就是說無論 a 是奇數還是偶數, a//2的計算結果都是交替出現的奇數和偶數,并且最終都會得到 a = 1 計算出 rem,然后結束循環,返回 rem。因此只需要在 a 為奇數的時候計算 rem 即可保證最后得到的 rem 就是我們想要的值。
參考文章:
面試題14- II. 剪繩子 II(數學推導 / 貪心思想 + 快速冪求余,清晰圖解)
快速冪算法(全網最詳細地帶你從零開始一步一步優化)
總結
以上是生活随笔為你收集整理的【面试相关】python实现快速幂取余算法详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Git 查看某个版本修改了哪些文件
- 下一篇: websocket python爬虫_p