前言
在开发中,计算乘方,我们一般是使用 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二进制形式下的位置 m 有关,而与 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 }