1. 使用快速幂算法实现Pow(x,n)
求x的n次方,可以使用暴力解法,这种算法时间复杂度为O(n),并且,当x和n比较大的时候,可能会存在溢出。
可以使用快速幂的算法
思路
在计算机中,十进制可以和二进制进行转换,利用该原理可将指数转为
通过快速幂可将指数运算时间复杂度由O(n)降低到O(longn)。
计算xn,将n写成2进制,在计算机中,n就是以2进制的形式存储的,比如计算315,15的二进制形式是1111
(0省略)23 + 22 + 21,315 = 38 * 34 * 32 * 31 ,这样一来,可以利用迭代法计算315,先算出32,根据32,算出34…,就不需要遍历15次,只需要进行4次循环,也就是n的二进制左边最后一个为1所在的位数。利用n & 1 == 1
来判断n的二进制数中每一位是否为1,为1的话就相乘,每次让n向右移一位,直到n=0。如果n为负数的话,就让x = 1 / x,n = -n
。
代码
public static double pow(double x, int n) {
//如果x为0,直接返回0
if(x == 0) return 0;
int b = n;
double res = 1;
//判断n是否为负数,是负数的话,就让x=1/x,b=-b
if(b < 0) {
x = 1 / x; //这里x必须是double类型的,如果是int类型的,x>2, 1 / x 就是0了
b = -b;
}
//开始循环,条件是b不为0
while(b > 0) {
//如果b的二进制数某一位是1,就需要res = res * x
if ((b & 1) == 1) res = res * x;
//进行迭代计算
x = x * x;
//b 向右移1位
b >>= 1;
}
return res;
}
- 时间复杂度 O(logn) : 二分的时间复杂度为对数级别。
- 空间复杂度 O(1) : resres, bb 等变量占用常数大小额外空间。
最不好理解的就是
//开始循环,条件是b不为0
while(b > 0) {
//如果b的二进制数某一位是1,就需要res = res * x
if ((b & 1) == 1) res = res * x;
//进行迭代计算
x = x * x;
//b 向右移1位
b >>= 1;
}
比如求39,9转为二进制为1001,39 = 38 * 31 ,也就是只要二进制某一位上为1,就会参与运算,所以这里才会有这个判断if ((b & 1) == 1) res = res * x;
,每循环一次,让x = x * x;
,b >>= 1;
,x正好和二进制位数对应。
2. 快速幂取余
现在oj网站的题或者竞赛的题,如果a的b次幂且b很大,那么题中大多会让你把结果对一个数取余也就是求模,例如a^b%c这种,当然如果是考高精度的题除外。
首先要知道这个公式(a*b) Mod c = [(a Mod c)*(b Mod c)] Mod c
,所以,xn % m = (x % m)n Mod c,根据上面的快速幂算法,
xn = x1*b1 * x2*b2 * x4*b2 x8*b3 … (bn是0或1,看n的二进制对应的位置是0还是1)
xn Mod m = ( x1*b1 * x2*b2 * x4*b2 x8*b3 …) Mod m
根据公式可以得出,
xn Mod m = ( x1*b1 * x2*b2 * x4*b2 x8*b3 …) Mod m = ( x1*b1 Mod m )* (x2*b2 Mod m) * (x4*b2 Mod m ) * (x8*b3 Mod m) …) Mod m
观察每一项之间的关系
x2*b2 Mod m = [ (x1*b1 Mod m) * (x1*b1 Mod m) ] Mod m =( x1*b1 )2 Mod m
x4*b2 Mod m = [ ( x2*b2 Mod m ) * ( x2*b2 Mod m ) ] Mod m = ( x2*b2 )2 Mod m
x8*b3 Mod m = [ (x4*b2 Mod m ) * ( x4*b2 Mod m ) ] Mod m = ( x4*b2 )2 Mod m
…
也就是说,后一项是前一项的平方对m取模,根据递推关系,求出 xn Mod m
代码
//使用快速幂取余,这里n为正整数,x <<< m
public static int quickPowMod(int x , int n, int m) {
if(x == 0) return 0;
int res = 1;
while(n > 0) {
//如果二进制位为1,就参与运算
if ((n & 1) == 1) {
res = (res * x ) % m;
}
//进行递推,每一次循环,都要计算更新一次
x = (x * x ) % m;
//二进制位移,相当于从右到左读取位b0 b1 b2 b3 b4等等
n >>= 1;
}
return res;
}
另外,如果m很大的话,代码里的res、x一般都是long类型的,最后转为int
如有不足之处,欢迎指正,谢谢!