快速幂
快速幂一般用来解决 ab % 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 < 1018
缺陷:
-
计算中的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.降低时间复杂度
先看一下我们手算310是怎么算的?
310 = (32)5 = 95 = 9 * 94 = 9 * (92)2 = 9 * 812 ....
根据上面,我们很容易发现规律:
当指数是偶数时,让指数除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 :