适用: 值需要多次翻倍的场景。
题目描述:
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即, x n x^n xn)。不得使用库函数,同时不需要考虑大数问题。
题目链接:数值的整数次方
快速幂引出:
当 n 小于 0 时,结果为 x − n x^{-n} x−n 因此只需考虑 n 为正数的情况。
一般情况下,我们会直接计算 x ∗ x x * x x∗x 循环 n - 1 次,但当 n 非常大时,计算会很耗时。
其实,并不需要必须计算 n - 1 次才能得出结果,使用某些方法计算若干次(通常比 n - 1 次要小很多)就能得出答案,这种方法被称为快速幂方法。
以 x 77 x^{77} x77 为例:
快速幂 + 递归
x 77 x^{77} x77 可以分解为:
x x x -> x 2 x^2 x2 -> x 4 x^4 x4 -> x 8 x^8 x8 -> x 19 x^{19} x19 -> x 38 x^{38} x38 -> x 77 x^{77} x77
乍一看看不出规律,但从右向左看可以发现, x x x 上的指数是右侧一半的下取整。
其中 x x x -> x 2 x^2 x2 , x 2 x^2 x2 -> x 4 x^4 x4, x 4 x^4 x4 -> x 8 x^8 x8 , x 19 x^{19} x19 -> x 38 x^{38} x38 右侧可以由左侧的平方取得,右侧的指数为偶数
x 8 x^8 x8 -> x 19 x^{19} x19, x 38 x^{38} x38 -> x 77 x^{77} x77,右侧可以由左侧的平方在多乘一个 x x x 获取,右侧的指数为奇数。
综合其上,可以得到一个递推关系式:
当指数 n n n 为奇数时, x n x^n xn = x n / 2 x^{n/2} xn/2 * x n / 2 x^{n/2} xn/2 * x x x;
当指数 n n n 为偶数时, x n x^n xn = x n / 2 x^{n/2} xn/2 * x n / 2 x^{n/2} xn/2
当指数 n = = 0 n == 0 n==0 时, x 0 x^0 x0 = 1
// 快速幂应用
public double myPow(double x, int n) {
long N = n;
return n >= 0 ? quickMul(x, n) : 1.0 / quickMul(x, n);
}
public double quickMul(x, n){
if(n == 0){
return 1;
}
// 先算出 n 是一半的值
double y = quickMul(x, n / 2);
// 如果 n 是偶数,则
return n % 2 == 0 ? y * y : y * y * x;
}
快速幂 + 迭代
任何正整数都能用二进制表示,77 = 64 + 8 + 4 + 1,因此可以写成二进制 ( 1001101 ) 2 (1001101)_2 (1001101)2
而 x 77 x^{77} x77 = x x x * x 4 x^4 x4 * x 8 x^8 x8 * x 64 x^{64} x64
完整的是 x x x , x 2 x^2 x2, x 4 x^4 x4 x 8 x^8 x8 , x 16 x^{16} x16, x 32 x^{32} x32, x 64 x^{64} x64,可以看出就是在二进制为1的位置选择了。
因此可以迭代指数,同时计算 x ∗ = x x *= x x∗=x 得到相应的二进制指数中间结果,如果它的二进制末尾是为1,则保留,与存储最终结果的值相乘,直到指数为0。
// 快速幂应用
public double myPow(double x, int n) {
long N = n;
return n >= 0 ? quickMul(x, n) : 1.0 / quickMul(x, n);
}
// 迭代法
public double quick(double x, long n){
double ans = 1.0;
double res = x;
while(n != 0){
if(n % 2 == 1){
ans *= res;
}
res *= res;
n /= 2;
}
return ans;
}