1. 程式人生 > 實用技巧 >【夏弈的題解記錄】快速冪加速乘方

【夏弈的題解記錄】快速冪加速乘方

前言

  在開發中,計算乘方,我們一般是使用 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

+ 2*b2+ 4*b3+ ... ... + 2^m-2*bm-1+ 2^m-1*bm)

      = 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*bm2^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));}