算法练习—快速幂
首先说下,快速幂解决的是什么问题,核心问题是解决计算机 “受不了的数”的问题。例如下面这道题:
实现 pow(x, n) ,即计算 x 的整数 n 次幂函数。
1.在没有学习快速幂之前我大脑里只有一个思路,暴力解决。
import java.util.*;
class Solution {
public double myPow(double x, int n) {
if(x==0) return 0;
if(n<0){
x=1/x;
n=-n;
}
double res=1.0;
for(int i=0;i<n;i++){
res*=x;
}
return res;
}
}
这样解是可以得到答案的,但是一般都会报超出时间限制,因为当n无限增大时,需要循环的次数呈指数级上升,会达到一个非常恐怖的次数。
2.1什么是快速幂算法
快速幂算法又称为二进制取幂(Binary Exponentiation),能够大幅度的减少计算步骤,从而提升运算速度,是一个在logn的时间内计算 的小技巧,而暴力的计算需要 n 的时间。
2.2代码展示
import java.util.*;
class Solution {
public double myPow(double x, int n) {
if(n==0) return 1;
#当测试数据n=-2147483648时执行n=−n会因越界而赋值出错。
#解决方法是先将n存入long变量b后面用b操作即可
long b=n;
if(b<0){
x=1/x;
b=-b;
}
double res=1.0;
while(b>0){
#if((b&1)==1)位运算等同于if(b%2==1)
if((b&1)==1) res=res*x;
x=x*x;
#b>>=1位运算等同于b/=2
b>>=1;
}
return res;
}
}
3例题展示
在基本了解了快速幂算法后我们来看一道例题:
我们称一个数字字符串是 好数字 当它满足(下标从0开始偶数下标处的数字为偶数且奇数下标处的数字为质数 (2,3,5 或 7)。
比方说,"2582" 是好数字,因为偶数下标处的数字(2 和 8)是偶数且奇数下标处的数字(5 和 2)为质数。但 "3245" 不是好数字,因为3在偶数下标处但
不是偶数。给你一个整数 n ,请你返回长度为 n 且为好数字的数字字符串总数 。由于答案可能会很大,请你将它对10^9 + 7取余后返回 。一个数字字符串是
每一位都由0到9组成的字符串,且可能包含前导0 。
3.1例题分析
一开始在看到这个题是我一头雾水,完全不知道从哪里下手,应该是我比较蠢,根本没有想到这道题
的关键所在。其实大家仔细分析一下,可以发现,在这道题中,对于任意一个数字如果要满足好数字特
征,那么他的偶数位只能为(0,2,4,6,8),奇数位只能为(2,3,5,7),而对于一个n位数字,
他的偶数位一共为(n+1)/2位,奇数位为n/2位。所以对于一个n位数字,满足为好数字的总数为:
[5*(n+1)/2]*[4*n/2]。
如果n为奇数则:[5^(n+1)/2]*[4^n/2]=5*20^n/2。
如果n为偶数则:[5^(n+1)/2]*[4^n/2]=20^n/2。
其实分析到现在这道题已经完全变成求某个数的指数幂的运算,不过我们还是要注意一些细节,下面我
们来看代码展示。
3.2暴力解决
class Solution {
public int countGoodNumbers(long n) {
int mod=1000000007;
long res=(n&1)==1?5:1,power=n/2,base=20;
for(int i=0;i<n/2;i++){
res=res*base;
}
return (int)(res%mod);
}
}
这样解题虽然逻辑上没有问题,但是一定不会编译通过,因为当n无限大时,计算结果会远大于long
数据所能表示的最大数字,会造成数据溢出,而且此时循环次数也非常大,即使没有数据溢出也会超出时
间限制。
3.3取模定理
取模运算解决的多个大数相乘所得结果过大(计算机不支持)而取不了后几位的问题,取模定理我
也是第一次听,看了之后发现是真的牛,不愧是数学定理。
(a + b) % p = (a % p + b % p) % p (1)
(a - b) % p = (a % p - b % p ) % p (2)
(a * b) % p = (a % p * b % p) % p (3)
a ^ b % p = ((a % p)^b) % p (4)
这里仅需看这里面的(3),(a * b) % p = (a%p * b%p) % p,这个就厉害了,乘积的取模
等于各个因子取模相乘然就在取模,相当于把数都控制在 p-1 位,这计算数量就超级小了,有木有~
class Solution {
public int countGoodNumbers(long n) {
int mod=1000000007;
long res=(n&1)==1?5:1,power=n/2,base=20;
for(int i=0;i<n/2;i++){
res=res*base%mod;
}
return (int)(res%mod);
}
}
加上取模定理之后其实已经不会数据溢出了,但是还是会报超出时间限制,取模定理仅仅是将数据都
给控制在一定位数以内,让数可以计算出来,但是并没有简化计算次数。要是有简化计算步骤的算法就
更好了,接下来就可以使用能够缩短计算次数的快速幂算法。
3.4快速幂算法–最终方案
class Solution {
public int countGoodNumbers(long n) {
int mod=1000000007;
long res=(n&1)==1?5:1,power=n/2,base=20;
while(power>0){
if((power&1)==1) res=res*base%mod;
base=base*base%mod;
power>>=1;
}
return (int)res;
}
}