如何高效進(jìn)行模冪運(yùn)算

讀完本文,你可以去力扣拿下如下題目:

372.超級(jí)次方

-----------

今天來(lái)聊一道與數(shù)學(xué)運(yùn)算有關(guān)的題目末购,LeetCode 372 題 Super Pow,讓你進(jìn)行巨大的冪運(yùn)算藏否,然后求余數(shù)箫措。

int superPow(int a, vector<int>& b);

要求你的算法返回冪運(yùn)算 a^b 的計(jì)算結(jié)果與 1337 取模(mod,也就是余數(shù))后的結(jié)果垫桂。就是你先得計(jì)算冪 a^b,但是這個(gè) b 會(huì)非常大专控,所以 b 是用數(shù)組的形式表示的抹凳。

這個(gè)算法其實(shí)就是廣泛應(yīng)用于離散數(shù)學(xué)的模冪算法,至于為什么要對(duì) 1337 求模我們不管伦腐,單就這道題可以有三個(gè)難點(diǎn):

一是如何處理用數(shù)組表示的指數(shù)赢底,現(xiàn)在 b 是一個(gè)數(shù)組,也就是說(shuō) b 可以非常大柏蘑,沒辦法直接轉(zhuǎn)成整型幸冻,否則可能溢出。你怎么把這個(gè)數(shù)組作為指數(shù)咳焚,進(jìn)行運(yùn)算呢洽损?

二是如何得到求模之后的結(jié)果?按道理革半,起碼應(yīng)該先把冪運(yùn)算結(jié)果算出來(lái)碑定,然后做 % 1337 這個(gè)運(yùn)算。但問(wèn)題是又官,指數(shù)運(yùn)算你懂得延刘,真實(shí)結(jié)果肯定會(huì)大得嚇人,也就是說(shuō)六敬,算出來(lái)真實(shí)結(jié)果也沒辦法表示碘赖,早都溢出報(bào)錯(cuò)了。

三是如何高效進(jìn)行冪運(yùn)算外构,進(jìn)行冪運(yùn)算也是有算法技巧的普泡,如果你不了解這個(gè)算法,后文會(huì)講解典勇。

那么對(duì)于這幾個(gè)問(wèn)題,我們分開思考叮趴,逐個(gè)擊破割笙。

PS:我認(rèn)真寫了 100 多篇原創(chuàng),手把手刷 200 道力扣題目眯亦,全部發(fā)布在 labuladong的算法小抄伤溉,持續(xù)更新。建議收藏妻率,按照我的文章順序刷題乱顾,掌握各種算法套路后投再入題海就如魚得水了。

如何處理數(shù)組指數(shù)

首先明確問(wèn)題:現(xiàn)在 b 是一個(gè)數(shù)組宫静,不能表示成整型走净,而且數(shù)組的特點(diǎn)是隨機(jī)訪問(wèn)券时,刪除最后一個(gè)元素比較高效。

不考慮求模的要求伏伯,以 b = [1,5,6,4] 來(lái)舉例橘洞,結(jié)合指數(shù)運(yùn)算的法則,我們可以發(fā)現(xiàn)這樣的一個(gè)規(guī)律:

image

看到這说搅,我們的老讀者肯定已經(jīng)敏感地意識(shí)到了炸枣,這就是遞歸的標(biāo)志呀!因?yàn)閱?wèn)題的規(guī)呐螅縮小了:

    superPow(a, [1,5,6,4])
=>  superPow(a, [1,5,6])

那么适肠,發(fā)現(xiàn)了這個(gè)規(guī)律,我們可以先簡(jiǎn)單翻譯出代碼框架:

// 計(jì)算 a 的 k 次方的結(jié)果
// 后文我們會(huì)手動(dòng)實(shí)現(xiàn)
int mypow(int a, int k);

int superPow(int a, vector<int>& b) {
    // 遞歸的 base case
    if (b.empty()) return 1;
    // 取出最后一個(gè)數(shù)
    int last = b.back();
    b.pop_back();
    // 將原問(wèn)題化簡(jiǎn)候引,縮小規(guī)模遞歸求解
    int part1 = mypow(a, last);
    int part2 = mypow(superPow(a, b), 10);
    // 合并出結(jié)果
    return part1 * part2;
}

到這里侯养,應(yīng)該都不難理解吧!我們已經(jīng)解決了 b 是一個(gè)數(shù)組的問(wèn)題背伴,現(xiàn)在來(lái)看看如何處理 mod沸毁,避免結(jié)果太大而導(dǎo)致的整型溢出。

如何處理 mod 運(yùn)算

首先明確問(wèn)題:由于計(jì)算機(jī)的編碼方式傻寂,形如 (a * b) % base 這樣的運(yùn)算息尺,乘法的結(jié)果可能導(dǎo)致溢出,我們希望找到一種技巧疾掰,能夠化簡(jiǎn)這種表達(dá)式搂誉,避免溢出同時(shí)得到結(jié)果。

比如在二分查找中静檬,我們求中點(diǎn)索引時(shí)用 (l+r)/2 轉(zhuǎn)化成 l+(r-l)/2炭懊,避免溢出的同時(shí)得到正確的結(jié)果。

那么拂檩,說(shuō)一個(gè)關(guān)于模運(yùn)算的技巧吧侮腹,畢竟模運(yùn)算在算法中比較常見:

(a * b) % k = (a % k)(b % k) % k

證明很簡(jiǎn)單,假設(shè):

a = Ak +B稻励;b = Ck + D

其中 A,B,C,D 是任意常數(shù)父阻,那么:

ab = ACk^2 + ADk + BCk +BD

ab % k = BD % k

又因?yàn)椋?/p>

a % k = B;b % k = D

所以:

(a % k)(b % k) % k = BD % k

綜上望抽,就可以得到我們化簡(jiǎn)求模的等式了加矛。

換句話說(shuō),對(duì)乘法的結(jié)果求模煤篙,等價(jià)于先對(duì)每個(gè)因子都求模斟览,然后對(duì)因子相乘的結(jié)果再求模

那么擴(kuò)展到這道題辑奈,求一個(gè)數(shù)的冪不就是對(duì)這個(gè)數(shù)連乘么苛茂?所以說(shuō)只要簡(jiǎn)單擴(kuò)展剛才的思路已烤,即可給冪運(yùn)算求模:

int base = 1337;
// 計(jì)算 a 的 k 次方然后與 base 求模的結(jié)果
int mypow(int a, int k) {
    // 對(duì)因子求模
    a %= base;
    int res = 1;
    for (int _ = 0; _ < k; _++) {
        // 這里有乘法,是潛在的溢出點(diǎn)
        res *= a;
        // 對(duì)乘法結(jié)果求模
        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;
}

你看味悄,先對(duì)因子 a 求模草戈,然后每次都對(duì)乘法結(jié)果 res 求模,這樣可以保證 res *= a 這句代碼執(zhí)行時(shí)兩個(gè)因子都是小于 base 的侍瑟,也就一定不會(huì)造成溢出唐片,同時(shí)結(jié)果也是正確的。

至此涨颜,這個(gè)問(wèn)題就已經(jīng)完全解決了费韭,已經(jīng)可以通過(guò) LeetCode 的判題系統(tǒng)了。

但是有的讀者可能會(huì)問(wèn)庭瑰,這個(gè)求冪的算法就這么簡(jiǎn)單嗎星持,直接一個(gè) for 循環(huán)累乘就行了?復(fù)雜度會(huì)不會(huì)比較高弹灭,有沒有更高效地算法呢督暂?

有更高效地算法的,但是單就這道題來(lái)說(shuō)穷吮,已經(jīng)足夠了逻翁。

因?yàn)槟阆胂耄{(diào)用 mypow 函數(shù)傳入的 k 最多有多大捡鱼?k 不過(guò)是 b 數(shù)組中的一個(gè)數(shù)八回,也就是在 0 到 9 之間,所以可以說(shuō)這里每次調(diào)用 mypow 的時(shí)間復(fù)雜度就是 O(1)驾诈。整個(gè)算法的時(shí)間復(fù)雜度是 O(N)缠诅,N 為 b 的長(zhǎng)度。

但是既然說(shuō)到冪運(yùn)算了乍迄,不妨順帶說(shuō)一下如何高效計(jì)算冪運(yùn)算吧管引。

PS:我認(rèn)真寫了 100 多篇原創(chuàng),手把手刷 200 道力扣題目闯两,全部發(fā)布在 labuladong的算法小抄褥伴,持續(xù)更新。建議收藏生蚁,按照我的文章順序刷題噩翠,掌握各種算法套路后投再入題海就如魚得水了戏自。

如何高效求冪

快速求冪的算法不止一個(gè)邦投,就說(shuō)一個(gè)我們應(yīng)該掌握的基本思路吧。利用冪運(yùn)算的性質(zhì)擅笔,我們可以寫出這樣一個(gè)遞歸式:

image

這個(gè)思想肯定比直接用 for 循環(huán)求冪要高效志衣,因?yàn)橛袡C(jī)會(huì)直接把問(wèn)題規(guī)模(b 的大型驮)直接減小一半,該算法的復(fù)雜度肯定是 log 級(jí)了念脯。

那么就可以修改之前的 mypow 函數(shù)狞洋,翻譯這個(gè)遞歸公式,再加上求模的運(yùn)算:

int base = 1337;

int mypow(int a, int k) {
    if (k == 0) return 1;
    a %= base;

    if (k % 2 == 1) {
        // k 是奇數(shù)
        return (a * mypow(a, k - 1)) % base;
    } else {
        // k 是偶數(shù)
        int sub = mypow(a, k / 2);
        return (sub * sub) % base;
    }
}

雖然對(duì)于題目绿店,這個(gè)優(yōu)化沒有啥特別明顯的效率提升吉懊,但是這個(gè)求冪算法已經(jīng)升級(jí)了,以后如果別人讓你寫冪算法假勿,起碼要寫出這個(gè)算法借嗽。

至此,Super Pow 就算完全解決了转培,包括了遞歸思想以及處理模運(yùn)算恶导、冪運(yùn)算的技巧,可以說(shuō)這個(gè)題目還是挺有意思的浸须,你有什么有趣的題目惨寿,不妨留言分享一下。

_____________

我的 在線電子書 有 100 篇原創(chuàng)文章删窒,手把手帶刷 200 道力扣題目裂垦,建議收藏!對(duì)應(yīng)的 GitHub 算法倉(cāng)庫(kù) 已經(jīng)獲得了 70k star易稠,歡迎標(biāo)星缸废!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市驶社,隨后出現(xiàn)的幾起案子企量,更是在濱河造成了極大的恐慌,老刑警劉巖亡电,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件届巩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡份乒,警方通過(guò)查閱死者的電腦和手機(jī)恕汇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)或辖,“玉大人瘾英,你說(shuō)我怎么就攤上這事∷滔荆” “怎么了缺谴?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)耳鸯。 經(jīng)常有香客問(wèn)我湿蛔,道長(zhǎng)膀曾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任阳啥,我火速辦了婚禮添谊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘察迟。我一直安慰自己斩狱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布扎瓶。 她就那樣靜靜地躺著喊废,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栗弟。 梳的紋絲不亂的頭發(fā)上污筷,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音乍赫,去河邊找鬼瓣蛀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛雷厂,可吹牛的內(nèi)容都是我干的惋增。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼改鲫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼诈皿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起像棘,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤稽亏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后缕题,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體截歉,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年烟零,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瘪松。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锨阿,死狀恐怖宵睦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情墅诡,我是刑警寧澤壳嚎,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響诬辈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜荐吉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一焙糟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧样屠,春花似錦穿撮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至业踢,卻和暖如春栗柒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背知举。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工瞬沦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雇锡。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓逛钻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親锰提。 傳聞我的和親對(duì)象是個(gè)殘疾皇子曙痘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容