实现函数
double Power(double base, int exponent)
,求base
的exponent
次方。不得使用库函数。同时不需要考虑大数问题。
此题来源于《剑指offer》面试题
这个题很具魔性,你准备好了嘛?
前方高能!!! 快快对号入座吧!
小伙伴,你是否觉得这道题很简单?于是在三十秒内写出了下列的代码:请对号入座
double myPow(double x, int n) {
double ans = 1.0;
for (int i = 1; i <= n; i++) {
ans *= x;
}
return ans;
}
或者:
double myPow(double x, int n) {
if (n == 1)return x;
return x * myPow(x, n - 1);
}
(。・∀・)ノ゙嗨,小伙伴,是你嘛?
面试官:我好像没有说n一定为正数吧? |
于是你重整旗鼓,又在三十秒内写出了下列代码:请对号入座
double myPow(double x, int n) {
double ans = 1.0;
for (int i = 1; i <= abs(n); i++) {
ans *= x;
}
return n>0?ans:1/ans;
}
或者:
double myPow(double x, int n) {
if (n == 1) return x;
if (n == -1) return 1 / x;
return n > 0 ? x * myPow(x, n - 1) : (1 / x) * myPow(x, n + 1);
}
(。・∀・)ノ゙嗨,小伙伴,是你嘛?
面试官:我好像也没说x不可以为0吧? |
恍然大悟的你,开始思索,如果0传入就没有意义了呀?我怎样处理呢?
于是忽然发现了什么,虽然没有意义,但我们也要象征性的表示我们考虑了这个值吧?我们可以返回0,于是写出了下面的代码:
double myPow(double x, int n) {
if (x == 0) return 0;//表示考虑到了这一情况,并不代表实际计算出了值
double ans = 1.0;
for (int i = 1; i <= abs(n); i++) {
ans *= x;
}
return n>0?ans:1/ans;
}
或者:
double myPow(double x, int n) {
if (x == 0) return 0;//表示考虑到了这一情况,并不代表实际计算出了值
if (n == 1)return x;
return x * myPow(x, n - 1);
}
感觉已经完全考虑到了所有的情况,应该是没问题了,然后开始暗暗的责怪自己,怎么这么简单的问题这么不小心。
面试官:给你一组数据,自己测试一下: 0.00001 2147483647 |
这,,不是说不需要大数问题吗??
这其实好像也是int
型的最大范围,严格意义上应该也不算是大数问题。
先运行一下…
已崩溃,说明是算法的问题,需要对算法进行优化,怎么优化呢?
细细想来,这个O(N)
好像真的没办法优化了,又不能使用库函数。
我们仔细观察一下幂运算的过程,如果计算3
的50
次幂,怎么办呢?
做50
次乘法肯定是最差的想法,可不可以优化一下呢?
如果我们计算3^25 * 3^25
,然后再计算3^12 * 3^12 *3
,这样计算是不是会少很多运算量呢?
仔细想想,时间复杂度已经降低为了O(logN)
。
思路是这样的:
x^n=x^(n/2) * x^(n/2)
如果n
是偶数。x^n=x^(n/2) * x^(n/2) *x
如果n
是奇数。有了这条思路,于是写出下面的代码:
double myPow(double x, int n) {
if (x==0) return 0;
double ans = 1;
if (n < 0) {
x = 1 / x;
n = -n;
}
while (n > 0) {
if (n%2==1) ans *= x;
x *= x;
n /= 1;
}
return ans;
}
或者:
double myPow(double x, int n) {
if (x==0) return 0;
if(n<0){
x=1/x;
n=-n;
}
return toPow(x,n);
}
double toPow(double x, int n) {
if (n == 0) return 1;
double half = toPow(x, n/2);
if (n%2==1) return half * half * x;
else return half * half;
}
这次应该没有问题了,算法已经优化得比较好了。
面试官:再给你一组数据:2.00000 -2147483648,测试一下 |
运行一下,咦,出错了,这,,仔细思考一下。。。
原来是int
的边界值忽略了,负边界转向正边界时,会溢出,呀,真不小心,感觉补出下面的代码:
double myPow(double x, int n) {
if (x==0) return 0;
long exp=n;
double ans = 1;
if (exp < 0) {
x = 1 / x;
exp = -exp;
}
while (exp > 0) {
if (exp%2==1) ans *= x;
x *= x;
exp /= 1;
}
return ans;
}
或者:
double myPow(double x, int n) {
if (x==0) return 0;
long exp=n;
if(exp<0){
x=1/x;
exp=-exp;
}
return toPow(x,exp);
}
double toPow(double x, long n) {
if (n == 0) return 1;
double half = toPow(x, n/2);
if (n%2==1) return half * half * x;
else return half * half;
}
这个应该再没有可以挑剔的地方了,是的,应该完全没有了,对的。
面试官:其实你上面犯了一个错误,一直没有提醒你,你可以找出来吗? |
仔细想想,好像没有犯什么错误了,把代码看过来看过去,看过来看过去,不可能有错啊。
崩溃的边缘,终究是一个人抗下了所有。
无奈,彷徨,生无可恋…
你是怎么判断两个数相等的? |
噢噢,终于发现了,原来,原来,原来,原来double
不能用==
判断,原因是计算机的小数都是不准确的,只能精确到一定数值,所以,当两数之差小于精确值,我们就可以认定相等了。
恍然大悟。
double myPow(double x, int n) {
if (n == 0) return 1.0;
if (n == 1) return x;
if (abs(x - 1) < 1e-15) return 1.0;
if (abs(x - 0) < 1e-15) return 0.0;
long exp = n;
double ans = 1;
if (exp < 0) {
x = 1 / x;
exp = -exp;
}
while (exp > 0) {
if ((exp & 1) == 1) ans *= x;
x *= x;
exp >>= 1;
}
return ans;
}
double myPow(double x, int n) {
if (n == 0) return 1.0;
if (n == 1) return x;
if (abs(x - 1) < 1e-15) return 1.0;
if (abs(x - 0) < 1e-15) return 0.0;
long exp = n;
double ans = 1;
if (exp < 0) {
x = 1 / x;
exp = -exp;
}
return toPow(x, exp);
}
double toPow(double x, long n) {
if (n == 0) return 1.0;
double half = toPow(x, n >> 1);
if ((n & 1) == 1) return half * half * x;
else return half * half;
}
真实的面试其实并不是有这么多的机会,有可能你的一下失误,瞬间失去了面试官对你的兴趣,所以,思维变得完善起来吧!
我们将其中得到的启示提取出来:
快速幂
的思想,也包含二分
的思想。==
来判断是否相等,计算机的所有小数都是不精确的,我们只要确定误差小于精确值,就可以认为是相等的。a&1
这个与运算,其实是对2取余。a>>1
这个位运算,其实是除2。ATFWUS --Writing By 2020–03–27