【夏弈的題解記錄】快速冪加速乘方
前言
在開發中,計算乘方,我們一般是使用 Math.pow()->double 方法來進行計算,這個方法最終呼叫的是一個本地方法 StrictMath.pow->double (Java)。
那如果要我們自己進行計算呢?原理非常簡單,a的n次方就是n個a相乘,當n小於0的時候,就是先計算乘方 -n 的結果,再將結果作為除數去除1即可:
1 class Solution {
2 public double myPow(double x, int n) {
3 if( n==0 || x==1.0){ return 1.0; }
4 if (n<0){ return 1/(x*myPow(x,(n+1)*-1)); } // 針對如Integer.MIN_VALUE的情況,避免因為正負轉換導致的越界問題
5 double res=x;
6 while(n-->1){
7 res*=x;
8 }
9 return res;
10 }
11 }
但如果就這麼實現,當n很大時時候(比如Integer.MAX_VALUE),求解的過程就要進行n-1次乘法,那整個計算過程消耗的時間就非常可觀了,如果在力扣中,還會出現下面的問題:
這個時候,我們就可以利用 快速冪 來實現乘方的加速,求解 a的n次方 時,使用快速冪,可以將演算法的時間複雜度降低到 O(log2n)。
1. 示例題目描述
原題:力扣劍指 Offer 16. 數值的整數次方
2. 原理描述
當我們計算 x的n次方時,針對指數n,假設n對應的二進位制數為:bm bm-1 bm-2 ... ... b2 b1,根據二進位制轉十進位制的演算法,可以得到:
n = 1*b1 + 2*b2 + 4*b3 + ... ... + 2^m-2*bm-1 + 2^m-1*bm
從而有
x^n = x^(1*b1
= x^1*b1 * x^2*b2 * x^4*b3 * ... ... * x^2^m-2*bm-1 * x^2^m-1*bm
這樣,我們就可以把求 x^n 的計算過程從 n-1 次乘法運算 降低到了 log2n次乘法運算。因為 bm 是 指數n 二進位制形式下的一個數字,所以 bm 的值只有兩種情況:1 或者 0。
1)當 bm=1 的時候,x^2^m-1*bm=x^2^m-1;
2)當bm=0 的時候,x^2^m-1*bm=x^0 = 1。
但我們可以注意到的是,x^2^m-1*bm中的2^m-1 部分只和 bm 在指數n二進位制形式下的位置有關,而與 bm 的值無關。
我們可以從 b1 開始運算,而b1也就對應著 指數n 二進位制形式下的第1位(最左一位)。執行完這次乘法後,對n執行一次 左移 >> 運算,使得 b1 被捨棄掉,b2來到第1位,而此時x^2^m-1*bm 中的2^m-1 部分(在這裡,我們將這部分稱之為 k )也要隨著乘法乘一次2,從而由x^1*b1中的 k=1(2^0) 變為 k=2(2^1),這使得我們在進行第二次乘法時,乘數就變成了:
x^k*b2 =x^2*b2
重複這個過程,直到 n==0 , 這意味著我們已經完成了整個運算過程,現在的計算結果就是乘方的結果。
3. 實現
每次乘法中,首先要判斷 當前位的 bm 是1還是0,我們通過 n&1 計算來進行判斷,只有當第一位為 1 的時候,n&1 的計算結果才是1。我們用 res 記錄每次乘法的結果,當 bm=0 的時候,本輪乘法中 res只需要乘 1 即可(可省略),否則就要乘上x^2^m-1*bm。但無論bm是不是0,作為x^2^m-1*bm 中2^m-1 的部分,x都要進行一次自乘。然後我們將 n 右移一位。重複這個過程,而跳出迴圈的條件就是 n==0。
實現可以參考下面給出的程式碼(Java):
1 class Solution {
2 public double myPow(double x, int n) {
3 if( n==0 || x==1.0){ return 1.0; }
4 // 指數為負時的處理
5 // 為了應對 Integer.MIN_VALUE 的情況,所以將指數加1,避免越界
6 if(n<0){ return 1/(x*myPow(x,(n+1)*-1)); }
7 double res=1;
8 // 快速冪加速乘法,可以指數降低乘法次數
9 while(n>0){
10 if((n&1)==1){
11 res*=x;
12 }
13 x*=x;
14 n>>=1;
15 }
16 return res;
17 }
18 }
if(n<0){return1/(x*myPow(x,(n+1)*-1));}