问题描述
如何在较快的时间内计算 x n x^n xn,其中 n n n可以是正数或负数,或0
- 暴力计算时间复杂度很高!
- 考虑采用快速幂算法求解。
快速幂算法(Divide and conquer )
- 只考虑 n n n为正数的情况
- 对于负数的情况只需要先求出正数的情况,再求倒数
- 对于
x
n
x^n
xn的情形,考虑划分如下子问题
- 将 x n x^n xn对半剖分,即分成解答 x ⌊ n / 2 ⌋ x^{\lfloor n/2 \rfloor} x⌊n/2⌋和 x n − ⌊ n / 2 ⌋ x^{n - \lfloor n/2 \rfloor} xn−⌊n/2⌋
- 分别求解两个部分。
- 当n是偶数时,有 ⌊ n / 2 ⌋ = n − ⌊ n / 2 ⌋ \lfloor n/2 \rfloor = n - \lfloor n/2 \rfloor ⌊n/2⌋=n−⌊n/2⌋
- 当n是奇数时,有 ⌊ n / 2 ⌋ + 1 = n − ⌊ n / 2 ⌋ \lfloor n/2 \rfloor +1= n - \lfloor n/2 \rfloor ⌊n/2⌋+1=n−⌊n/2⌋
- 因此给出了一种递归算法:
- 递归求解 x ⌊ n / 2 ⌋ x^{\lfloor n/2 \rfloor} x⌊n/2⌋
- 当n是偶数时直接将结果平方;
- 当n是奇数时,将上述结果求平方后再乘以x
- 每次求解子问题时将规模缩小了一半,因此复杂度为(logn)
class Solution {
public double myPow(double x, int n) {
double ans = quickpower(x,n);
return n<0?1/ans:ans;
}
public double quickpower(double x,long n)
{
if(n == 0)
return 1;
double ans = quickpower(x,n/2);
return n%2==0?ans*ans:ans*ans*x;
}
}
迭代形式
思路
- 上述算法还需要递归的栈的空间开销
- 但是迭代算法的栈的空间开销为 O ( 1 ) O(1) O(1)
- 还是只在n>0的前提下讨论
考虑一下计算
x
77
x^{77}
x77的递归树,可以看到只有当结果乘以
x
x
x时,得到的结果才对最终结果有贡献。
- 如图所示,quickpow(x,0)的结果两两相乘,再乘以x,作为quickpow(x,1)的结果;quickpow(x,1)的结果再两两相乘,不乘以x作为quickpow(x,2)的结果;递归过程如此进行。
- 由于上述递归过程确实能够返回正确的结果
- 而注意到在递归计算过程中只在某些地方乘以了结果 x x x
- 而乘以了 x x x得到的结果将被用作上层递归的全部计算
- 例如考虑在quickpow(x,4)层对应乘的x值
- 比如quickpow(x,9)的值可以看作 q u i c k p o w ( x , 4 ) 2 ∗ x quickpow(x,4)^2 * x quickpow(x,4)2∗x
- 这个x对最终结果的贡献为 x 8 x^8 x8
- 显然,如果假定进入函数时为第0层,(例如在图中quick(77))为第0层,往下层数加1.在某层特定递归中乘的 x x x值对于最终结果的贡献为 x 2 n − 1 x^{2^{n-1}} x2n−1。
- 一般的,假设在递归的第 k 1 , k 2 ⋯ k m k_1,k_2\cdots k_m k1,k2⋯km层都乘以了x,那么我们的最终结果为: x 2 k 1 − 1 x 2 k 2 − 1 ⋯ x 2 k m − 1 x^{2^{k_1 - 1}} x^{2^{k_2 - 1}} \cdots x^{2^{k_m -1}} x2k1−1x2k2−1⋯x2km−1如果假定需要求的结果为 x n x^n xn,那么显然具有关系 2 k 1 − 1 + 2 k 2 − 1 + ⋯ + 2 k m − 1 = n 2^{k_1-1} + 2^{k_2-1} + \cdots + 2^{k_m - 1} = n 2k1−1+2k2−1+⋯+2km−1=n
- 观察上述式子:
- 显然和n的二进制表示有关系!
- 对于任何正整数n的二进制表示是唯一的!而将二进制转换为十进制的方法就是将二进制表示中为1的位乘以对应的次幂.即对于上述 k 1 , k 2 ⋯ k m k_1,k_2\cdots k_m k1,k2⋯km来说都是唯一的,就是对应于正整数n的二进制表示中为1的位次序!
- 所以我们可以唯一的求出 k 1 , k 2 ⋯ k m k_1,k_2\cdots k_m k1,k2⋯km,即在哪一层递归中乘以x
- 由于给出了哪些步骤需要乘以x,于是可以给出迭代算法,自底向上,按照上述递归树模拟递归算法的进行!
代码
class Solution {
public double myPow(double x, int n) {
if(n == 0)
return 1;
long n_1 = Math.abs((long)n);
//获取n的二进制比特位数
int i;
double ans = 1;
for(i = 0;n_1>>i!=0;i++); //二进制比特位数
long mask ;
if(i == 1)
mask = 1;
else mask = (long)(1 << (i - 2)) * 2;
while(mask!=0)
{
if((n_1 & mask)!=0)
{
ans = ans * ans * x;
}
else ans = ans*ans;
mask = mask>>1;
}
return n<0?1/ans:ans;
}
}