目录
快速幂
快速幂一般用来解决 a^b % c 的问题,当然计算不模c的情况也行。
1.暴力
// 返回结果a^b % c (c的范围一般很小所以用了int)
public long pow(long a, long b, int c)
{
long res = 1;
for (long i = 0 ; i < b; i++)
{
res *= a;
}
return res % c;
}
这种方法对于数据范围小的情况还是可以的,但在特殊的情况下b的范围很大,例如 0 < b < 10^18
缺陷:
-
计算中的res很容易超过long
-
时间复杂度高 O(N)
2.解决溢出问题
取模运算有个性质:(a * b) % p = ((a % p) * (b % p)) % p
下面我们来简单证明下:
设:a = k1 * p + q1
b = k2 * p + q2
(a * b) = (...) * p + q1 * q2
即 (a * b) % p = (q1 * q2) % p (这里不知道q1 * q2是否大于p,所以还要取模一下)
又因为 a % p = (k1 * p + q1) % p = q1
b % p = (k2 * p + q2) % p = q2
所以 (a * b) % p = ((a % p) * (b % p)) % p
但这有什么用呢?
我们之前是( a * a * ...) % c,根据这个性质我们就可以边乘边对c取摸了,这样不就解决了溢出问题了吗
即:ans *= a; ans %= c;
但还有缺陷:
-
时间复杂度的问题没有解决
3.降低时间复杂度
先看一下我们手算3^10是怎么算的?
3^10 = (3^2)^5 = 9^5 = 9 * 9^4 = 9 * (9^2)^2 = 9 * 81^2 ....
根据上面,我们很容易发现规律:
当指数是偶数时,让指数除2,底数平方。
当指数是奇数的时候让res乘一个底数,在让指数除2,底数平方
这个算法是时间复杂度是多少? 每次让指数 b /= 2,显然是O(logN)
4.代码实现
1.详细版
public long quickPow(long a, long b, int c)
{
a %= c; // 防止一开始a过大
long res = 1; // 其实这里用int也行
while(b > 0)
{
if (b % 2 == 1) // 指数是奇数
{
res = (res * a) % c; // 边乘边摸
a = (a * a) % c;
b /= 2;
}
else // 指数是偶数
{
a = (a * a) % c;
b /= 2;
}
}
return res;
}
2.简洁版+位运算优化
public long quickPow(long a, long b, int c)
{
a %= c;
long res = 1;
while(b > 0)
{
if ((b & 1) == 1) // b % 2 == 1
{
res = (res * a) % c;
}
a = (a * a) % c;
b >>= 1; // b /= 2;
}
return res;
}
5.课后习题
leetcode :