文章目录
- 迭代实现快速幂
- 思路
- int的取值范围
- 快速幂
- 从二进制的角度来理解
- 从二分法的角度来理解
- 代码
- 复杂度分析
- 进阶——超级次方
- 思路
- 倒序+快速幂
- 正序+快速幂
- 代码
- 复杂度分析
迭代实现快速幂
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:
输入:x = 2.10000, n = 3
输出:9.26100
示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25
提示:
-100.0 < x < 100.0
-231 <= n <= 231-1
-104 <= xn <= 104
思路
想用累乘处理幂路子就走窄了,超时是板上钉钉的。
用快速幂的思想才符合题目考察的意图,在详说快速幂之前说一个很刁钻的测试用例 —— n=-2147483648
。
int的取值范围
我们知道:
- int的取值范围是
-2147483648
(-231)到2147483647
(231 - 1) -2147483648
有一位符号位可用,因此2147483647
是32位操作系统中最大的符号型整型常量。- 当出现这个刁钻的测试用例时,如果对n粗暴的取绝对值的话,int是容纳不下的,因此当
n<0
时, 需要定义一个long long
类型变量m
来存储n
的绝对值。
快速幂
从二进制的角度来理解
- 对于任何十进制正整数 n ,设其二进制为
则有:
- 根据以上推导,可把计算 xn 转化为解决以下两个问题:
- 因此,应用以上操作,可在循环中依次计算
的值,并将所有累计相乘即可。
从二分法的角度来理解
快速幂实际上是二分思想的一种应用。
- 二分推导: xn = xn/2 × xn/2 = (x2)n/2,令
n/2
为整数,则需要分为奇偶两种情况(设向下取整除法符号为 “//” ):
- 幂获取结果:
- 根据二分推导,可通过循环 x = x2 操作,每次把幂从
n
降至n//2
,直至将幂降为0
; - 设
sum=1
,则初始状态 xn = xn × sum。在循环二分时,每当n
为奇数时,将多出的一项x
乘入sum
,则最终可化至 xn = x0 * sum = sum,返回sum
即可。
转自作者:jyd
算法流程:
- 当
x = 0
时:直接返回 0 (避免后续x = 1 / x
操作报错)。 - 初始化
sum = 1
; - 当
n < 0
时:把问题转化至n≥0
的范围内,即执行x = 1/x
,n = - n
; - 循环计算:当
n = 0
时跳出;
- 当
n&1=1
时:将当前x
乘入sum
(即sum∗=x
); - 执行 x = x2(即
x∗=x
); - 执行
n
右移一位(即n>>=1
)。
- 返回 sum。
代码
class Solution {
public:double myPow(double x, int n) {if(x == 0) return 0;double sum = 1;long long m = n;if(n < 0){x = 1.0/x;m = -m;}while(m > 0){if(m & 1){sum *= x;}x *= x;m >>= 1;}return sum;}
};
复杂度分析
- 时间复杂度 O(log_2 n): 二分的时间复杂度为对数级别。
- 空间复杂度 O(1): sum, b 等变量占用常数大小额外空间。
进阶——超级次方
计算 ab 对 1337
取模,a 是一个正整数,b 是一个非常大的正整数且会以数组形式给出。
思路
本题的难点在于对 数组b
的处理,显然无法将其转为 一个具体类型,因此解决方法无非就是 在 倒序 or 正序 遍历数组的同时进行次方处理。
倒序+快速幂
本质无非就是把上面提到的二进制思想转为十进制,具体来说:
223 可以看作 2(10 * 2) + (1 * 3) ,也就是 10242 * 23 。
那么我们只需要在第 k
次 倒序遍历 数组 b
的同时,记录 a
的 10k-1 次方即可。
正序+快速幂
思路源于秦九韶算法,一般地,一元 n
次多项式的求值需要经过 (n+1)∗n2\frac{(n+1)*n}{2}2(n+1)∗n 次乘法和 nnn 次加法,而秦九韶算法只需要 nnn 次乘法和 nnn 次加法。大大简化了运算过程。
把一个 n 次多项式:
改写成如下形式:
举个具体的例子,计算 22342^{234}2234:
2234=2200+30+4=2200∗230∗24=(220∗23)10∗24=[(110∗22)10∗23]10∗242^{234} = 2^{200+30+4} = 2^{200} * 2^{30} * 2^4 = (2^{20} * 2^3)^{10} * 2^4 = [(1^{10} * 2^2)^{10} * 2^3]^{10} * 2^42234=2200+30+4=2200∗230∗24=(220∗23)10∗24=[(110∗22)10∗23]10∗24
最后一步推导实际上是对规律的归纳,即:以 1
作为初始值 res
,每次有 新项 加入时,对 res
执行 res=res10∗新项res = res^{10} * 新项res=res10∗新项 的操作。
代码
class Solution {const int MOD = 1337;int quickmul(int x, int n){ // 快速幂实现pow功能if(x == 0) return 0;int sum = 1;while(n){if(n&1) sum = (long)sum * x % MOD;n >>= 1;x = (long)x * x % MOD;}return sum;}
public:int superPow(int a, vector<int>& b) { // 倒序int res = 1, n = b.size();for(int i=n-1; i>=0; i--){res = (long)res * quickmul(a, b[i]) % MOD;a = quickmul(a, 10); // 记录当前 a 的幂}return res;}int superPow(int a, vector<int>& b) { // 正序int res = 1, n = b.size();for(int i : b){res = (long)quickmul(res, 10) * quickmul(a, i) % MOD;// 加入新项时,对 res 执行 res = res^10 * 新项 的操作}return res;}
};
复杂度分析
- 时间复杂度O(∑i=0n−1logbi\sum_{i=0}^{n-1}{\log{b_i}}∑i=0n−1logbi): 其中
n
是数组b
的长度,对每个 bib_ibi 计算快速幂的时间为 O(logbi\log{b_i}logbi)。 - 空间复杂度O(1): 过程由迭代实现,避免了递归方法对栈空间的占用,只需要常数的空间存放若干变量。