高效进行模幂运算
高效進行模冪運算
文章目錄
- 高效進行模冪運算
- 一、題目描述
- 二、分析
- 三、代碼
一、題目描述
你的任務是計算 a^b 對 1337 取模,a 是一個正整數,b 是一個非常大的正整數且會以數組形式給出。
示例 1:
輸入: a = 2, b = [3] 輸出: 8示例 2:
輸入: a = 2, b = [1,0] 輸出: 1024二、分析
單就這道題可以有三個難點:
- 一是如何處理用數組表示的指數,現在 b 是一個數組,也就是說 b可以非常大,沒辦法直接轉成整型,否則可能溢出。你怎么把這個數組作為指數,進行運算呢?
- 二是如何得到求模之后的結果?按道理,起碼應該先把冪運算結果算出來,然后做 % 1337 這個運算。但問題是,指數運算你懂得,真實結果肯定會大得嚇人,也就是說,算出來真實結果也沒辦法表示,早都溢出報錯了。
- 三是如何高效進行冪運算,進行冪運算也是有算法技巧的,如果你不了解這個算法,后文會講解。
那么對于這幾個問題,我們分開思考,逐個擊破。
如何處理數組指數:
- 首先明確問題:現在 b 是一個數組,不能表示成整型,而且數組的特點是隨機訪問,刪除最后一個元素比較高效。
- 不考慮求模的要求,以 b = [1,5,6,4] 來舉例,結合指數運算的法則,我們可以發現這樣的一個規律:
看到這,我們的老讀者肯定已經敏感地意識到了,這就是遞歸的標志呀!因為問題的規模縮小了:
那么,發現了這個規律,我們可以先簡單翻譯出代碼框架:
// 計算 a 的 k 次方的結果 // 后文我們會手動實現 int mypow(int a, int k);int superPow(int a, vector<int>& b) {// 遞歸的 base caseif (b.empty()) return 1;// 取出最后一個數int last = b.back();b.pop_back();// 將原問題化簡,縮小規模遞歸求解int part1 = mypow(a, last);int part2 = mypow(superPow(a, b), 10);// 合并出結果return part1 * part2; }到這里,應該都不難理解吧!我們已經解決了 b 是一個數組的問題,現在來看看如何處理 mod,避免結果太大而導致的整型溢出。
如何處理 mod 運算:
- 首先明確問題:由于計算機的編碼方式,形如 (a * b) % base這樣的運算,乘法的結果可能導致溢出,我們希望找到一種技巧,能夠化簡這種表達式,避免溢出同時得到結果。
- 比如在二分查找中,我們求中點索引時用 (l+r)/2 轉化成 l+(r-l)/2,避免溢出的同時得到正確的結果。
- 那么,說一個關于模運算的技巧吧,畢竟模運算在算法中比較常見:
(a * b) % k = (a % k)(b % k) % k
- 換句話說,對乘法的結果求模,等價于先對每個因子都求模,然后對因子相乘的結果再求模。
- 那么擴展到這道題,求一個數的冪不就是對這個數連乘么?所以說只要簡單擴展剛才的思路,即可給冪運算求模:
三、代碼
int base = 1337;// 計算 a 的 k 次方然后與 base 求模的結果 int mypow(int a, int k) {// 對因子求模a %= base;int res = 1;for (int _ = 0; _ < k; _++) {// 這里有乘法,是潛在的溢出點res *= a;// 對乘法結果求模res %= base;}return res; }int superPow(int a, vector<int>& b) {if (b.empty()) return 1;int last = b.back();b.pop_back();int part1 = mypow(a, last);int part2 = mypow(superPow(a, b), 10);// 每次乘法都要求模return (part1 * part2) % base; }- 你看,先對因子 a 求模,然后每次都對乘法結果 res 求模,這樣可以保證 res *= a 這句代碼執行時兩個因子都是小于base 的,也就一定不會造成溢出,同時結果也是正確的。
- 至此,這個問題就已經完全解決了,已經可以通過 LeetCode 的判題系統了。
但是有的讀者可能會問,這個求冪的算法就這么簡單嗎,直接一個 for 循環累乘就行了?復雜度會不會比較高,有沒有更高效地算法呢? - 有更高效地算法的,但是單就這道題來說,已經足夠了。
- 因為你想想,調用 mypow 函數傳入的 k 最多有多大?k 不過是 b 數組中的一個數,也就是在 0 到 9 之間,所以可以說這里每次調用 mypow 的時間復雜度就是 O(1)。整個算法的時間復雜度是 O(N),N 為 b 的長度。
如何高效求冪:
快速求冪的算法不止一個,就說一個我們應該掌握的基本思路吧。利用冪運算的性質,我們可以寫出這樣一個遞歸式:
- 這個思想肯定比直接用 for 循環求冪要高效,因為有機會直接把問題規模(b 的大小)直接減小一半,該算法的復雜度肯定是 log級了。
- 那么就可以修改之前的 mypow 函數,翻譯這個遞歸公式,再加上求模的運算:
總結
- 上一篇: 运用贪心思想解决跳跃游戏
- 下一篇: 二分查找的应用