解法1:避坑写法
double powRcu(double base, int exponent) {
if (exponent == 0) return 1.0; //必须有
if (exponent == 1) return base;
double res = powRcu(base, exponent/2); //奇数偶数除以2的值一样
res *= res;
if (exponent%2) res *= base;
return res;
}
double myPow(double x, int n){
if (x == 0) return 0;
if (n == 0) return 1;
bool n_sign = false;
if (n < 0) {
n_sign = true;
n = fabs(n);
}
bool x_sign = false;
if (x < 0) {
if (n%2) x_sign = true;
x = fabs(x);
}
double res = powRcu(x,n);
if (n_sign) res = 1.0/res;
if (x_sign) res *= -1;
return res;
}
此解法能成功的两个重要点:
1:powRcu()函数中的第一个if。
一开始总觉得这个判断没有必要,但是提交后总是在测试例为1.00000, -2147483648时候出现stack-overflow错误。百思不得解,一个正数一直除以2,得到1就达到退出条件了,还会得到别的数么?是的,前提得是一个正数!一个数的绝对值难道不是正数么?因为是计算机的int 类型!
1.1 复习下4种基本有符号数据类型的取值范围:
类型 | 取值范围(位表示) | 取值范围(正数表示) | 占字节数 |
byte | -2^7 ~ 2^7-1 | -128 ~ 127 | 1 |
short | -2^15 ~ 2^15-1 | -32768 ~ 32767 | 2 |
int | -2^31 ~ 2^31-1 | -2147483648 ~ 2147483647 | 4 |
long | -2^63 ~ 2^63-1 | -9223372036854774808 ~ 9223372036854774807 | 8 |
应该有所发现:有符号数的负数比正数多一个数,即最小的数。
1.2 再补充下fabs的基本知识:
double fabs(double x):参数和返回值都是double。区别于int abs(int x)。
求绝对值用fabs()或(0-x)等手段都没毛病,但是又赋值给int类型变量就出现问题了,等于没用!除非用一个范围更大的类型,如unsigned int 或者double类型得到算出的绝对值都不会出现 “诡异” 的问题。
综上,如果指数的绝对值赋值给int类型,powRcu()中第一个if语言是必须的,因为指数不断除以二不仅会得到1,还会得到-1(指数为int最小值-2147483648时)。如果指数的绝对值赋值给比int所容纳整数大的类型,则不需要powRcu()中第一个if,因为指数不断除以二只会得到1。
2:避免指数分奇偶处理
小试牛刀:主函数myPow()和解法1一致,看看如下powRcu实现出现栈溢出的问题是什么
double powRcu(double base, int exponent) {
if (exponent == 0)
return 1.0;
if (exponent == 1)
return base;
if (exponent%2) {
double tmp = powRcu(base, (exponent-1)/2);
return base * tmp * tmp;
}
else {
double tmp = powRcu(base, exponent/2);
return tmp * tmp;
}
}
函数第一个if也有,为什么就出错呢?是因为 if (exponent%2)和其else写的太繁琐或者重复了?还有别的怀疑么?确实还是在测试例为1.00000, -2147483648时出现的问题。
其实还有一个细节:一个负数取余,不同平台的实现可能不一致,亲测leetcode上负数取余的值还是负数。
问题就出在-1%2== -1。当-2147483648一直除以2除以2,得到-1时候其实计算已经完成了,但是没有这个返回条件,而if (exponent%2)此时又为真,执行(exponent-1)/2,给一个负数执行减法,相当于将|exponent|变得越来越大,最后就死循环了。
综上,负数结合%和-运算,有时候真是一种很 “玄妙” 的旋涡。
解法2:采取措施,避免指数为-2147483648的种种坑
double powRcu(double base, unsigned int exponent) { //指数为unsigned int
// if (exponent == 0) return 1.0; //不再需要
if (exponent == 1) return base;
if (exponent%2) {
double tmp = powRcu(base, (exponent-1)/2);
return base * tmp * tmp;
}
else {
double tmp = powRcu(base, exponent/2);
return tmp * tmp;
}
}
double myPow(double x, int n){
if (x == 0) return 0;
if (n == 0) return 1;
bool n_sign = false;
unsigned int N = 0; //用unsigned int来装绝对值
if (n < 0) {
n_sign = true;
N = fabs(n);
}
bool x_sign = false;
if (x < 0) {
if (n%2) x_sign = true;
x = fabs(x);
}
double res = 0.0;
if (N) res = powRcu(x,N); //分指数正负调用
else res = powRcu(x,n);
if (n_sign) res = 1.0/res;
if (x_sign) res *= -1;
return res;
}
相对于解法1,在区分指数为负时将其赋给了容纳正数数值更大的数,所有坑就都解决了,随便写都稳稳当当。
看到也有大佬在算法中区分对待-2147483648 示例,绝对值赋值为2147483647,即int能容纳的数值,为其单独写一个算法,其他的就是是另一个。虽然拓展性不强,但是针对这道题而言,也是一个思路。