Pow(x,n)---快速冪算法

題目

實現 pow(x, n) ,即計算 x 的 n 次冪函數。
示例:

輸入: 2.00000, 10
輸出: 1024.00000

輸入: 2.10000, 3
輸出: 9.26100

輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25

說明:

  • -100.0 < x < 100.0
  • n 是 32 位有符號整數,其數值範圍是 [−231, 231 − 1] 。

解決方案

分別是遞歸和迭代兩個版本,當指數 n 爲負數時,我們可以計算 x-n再取倒數得到結果,因此我們只需要考慮 n 爲自然數的情況。

思考過程(個人的錯誤解法,可略過)
考慮到計算n次冪,就想到用循環來解決,xn看作是y*x的n-1次循環(y0=x)。n小於0的情況下取n的絕對值,計算出循環後的值後用1除該值,即取倒數;n大於0時,若n爲1則計算結果爲x本身,其他情況則循環n-1次乘x;n等於0時,計算結果爲1 。

class Solution {
public:
    double myPow(double x, int n) {
        double y=x;
        if(n<0){
            for(n=-n;n-1>0;n--){
                y=y*x;
            }
            y=1/y;
        }
        else if(n==0){
            y=1;
        }
        else if(n==1){
            y=x;
        }
        else{
            for(;n-1>0;n--){
                y=y*x;
            }
        }
        return y;
    }
};

錯誤原因:暴力計算,效率太低導致程序超時。

方法一:快速冪+遞歸
「快速冪算法」的本質是分治算法。舉個例子,如果我們要計算 x64
,我們可以按照:

x→x2→x4→x8→x16→x32→x64

的順序,從 x 開始,每次直接把上一次的結果進行平方,計算 6 次就可以得到 x64的值,而不需要對 x 乘 63 次 x。

這是2的整數次冪,如果n是奇數或者不是2的整數次冪該怎麼辦呢?
比如 x77 ,可以按照:

x→x2→x4→x9→x19→x38→x77

從x4後就進行的是平方後乘 x,其中19到38是平方,直接從左到右進行推導看上去很困難,因爲在每一步中,我們不知道在將上一次的結果平方之後,還需不需要額外乘 x。但如果我們從右往左看,分治的思想就十分明顯了:

  • 要計算 xn,先算 y = xn/2,根據遞歸結果,n爲偶數時,xn = y2;n爲奇數時,n/2向下取整,xn = y2*x 。
  • 以0爲邊界,n爲0時,任意數的0次方均爲1.

由於每次遞歸都會使得指數減少一半,因此遞歸的層數爲 O(log n),算法可以在很快的時間內得到結果。

class Solution {
public:
    double quick(double x,long long n){
        double y;
        if(n==0){
            return 1.0;
        }
        y=quick(x,n/2);
        return n%2==0 ? y*y : y*y*x;
     }
    double myPow(double x, int n) {
        long long N = n;
        return n>=0 ? quick(x,N) : 1.0/quick(x,-N);
    }
};

方法二 快速冪+迭代
由於遞歸需要使用額外的棧空間,我們試着將遞歸轉寫爲迭代。在方法一中,我們也提到過,從左到右進行推導是不容易的,因爲我們不知道是否需要額外乘 xx。但我們不妨找一找規律,看看哪些地方額外乘了 xx,並且它們對答案產生了什麼影響。

還是以x77爲例:

x→x2→x4→+x9→+x19→x38→+x77

並且把需要額外乘 xx 的步驟打上了 + 標記。可以發現:

  • x38→+x77中額外乘的x在x77中貢獻了x
  • x9→+x19中額外乘的x在x之後平方了2次,在x77中貢獻了x的22次方,即x4
  • x4→+x9中額外乘的x在之後平方了3次,在x77中貢獻了x的23次方,即x8
  • 最初的x在之後平方了6次,因此在x77中貢獻了x的26次方,即x64

把這些貢獻相乘就得到了x77,而這些貢獻的指數部分又是什麼呢?它們都是 2 的冪次,這是因爲每個額外乘的 x 在之後都會被平方若干次。而這些指數 1,4,8 和 64,恰好就對應了 77 的二進制表示 (1001101)2中的每個 1!

因此我們藉助整數的二進制拆分,就可以得到迭代計算的方法,下面的代碼給出了詳細的註釋。

class Solution {
public:
    double quickMul(double x, long long N) {
        double ans = 1.0;
        // 貢獻的初始值爲 x
        double x_contribute = x;
        // 在對 N 進行二進制拆分的同時計算答案
        while (N > 0) {
            if (N % 2 == 1) {
                // 如果 N 二進制表示的最低位爲 1,那麼需要計入貢獻
                ans *= x_contribute;
            }
            // 將貢獻不斷地平方
            x_contribute *= x_contribute;
            // 捨棄 N 二進制表示的最低位,這樣我們每次只要判斷最低位即可
            N /= 2;
        }
        return ans;
    }

    double myPow(double x, int n) {
        long long N = n;
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }
};

參考文章
作者:LeetCode-Solution
鏈接:https://leetcode-cn.com/problems/powx-n/solution/powx-n-by-leetcode-solution/
來源:力扣(LeetCode)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章