数值的整数次方--一道题培养你的严密思维

0x01.问题

实现函数double Power(double base, int exponent),求baseexponent次方。不得使用库函数。同时不需要考虑大数问题。

此题来源于《剑指offer》面试题

这个题很具魔性,你准备好了嘛?

前方高能!!! 快快对号入座吧!

0x02.First blood – :正?

小伙伴,你是否觉得这道题很简单?于是在三十秒内写出了下列的代码:请对号入座

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一定为正数吧?

数值的整数次方--一道题培养你的严密思维_第1张图片

0x03.Double kill – :不为0?

于是你重整旗鼓,又在三十秒内写出了下列代码:请对号入座

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吧?

数值的整数次方--一道题培养你的严密思维_第2张图片

0x04.Triple kill – :大数?

恍然大悟的你,开始思索,如果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

数值的整数次方--一道题培养你的严密思维_第3张图片

0x05.Quadra kill – :边界值?

这,,不是说不需要大数问题吗??
这其实好像也是int型的最大范围,严格意义上应该也不算是大数问题。
先运行一下…

  • 咦,迭代超时;
  • 咦,递归栈溢出;

已崩溃,说明是算法的问题,需要对算法进行优化,怎么优化呢?

细细想来,这个O(N)好像真的没办法优化了,又不能使用库函数。

我们仔细观察一下幂运算的过程,如果计算350次幂,怎么办呢?

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,测试一下

数值的整数次方--一道题培养你的严密思维_第4张图片

0x06.Penta kill – :==?

运行一下,咦,出错了,这,,仔细思考一下。。。

原来是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;
}

这个应该再没有可以挑剔的地方了,是的,应该完全没有了,对的。

面试官:其实你上面犯了一个错误,一直没有提醒你,你可以找出来吗?

数值的整数次方--一道题培养你的严密思维_第5张图片

0x07.压死骆驼的最后一根稻草

仔细想想,好像没有犯什么错误了,把代码看过来看过去,看过来看过去,不可能有错啊。

崩溃的边缘,终究是一个人抗下了所有。

无奈,彷徨,生无可恋…

你是怎么判断两个数相等的?
小朋友,你是否有很多问号???

噢噢,终于发现了,原来,原来,原来,原来double不能用==判断,原因是计算机的小数都是不准确的,只能精确到一定数值,所以,当两数之差小于精确值,我们就可以认定相等了。

恍然大悟。

0x08.最佳代码

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;
}
时间复杂度: O(logN)
空间复杂度:O(1)
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;
}
时间复杂度:O(logN)
空间复杂度:O(logN)

0x09.最终感悟

真实的面试其实并不是有这么多的机会,有可能你的一下失误,瞬间失去了面试官对你的兴趣,所以,思维变得完善起来吧!

我们将其中得到的启示提取出来:

  • 没有给出具体限制范围的数字一定要考虑做数学运算的多可能性。
  • 一定要注意在一定范围内的所要做的优化。
  • 正负边界值是有一个差值的,所以,不要随意将负边界向正边界转,会造成溢出。
  • 上述的优化思想其实也是快速幂的思想,也包含二分的思想。
  • 不要直接对浮点数用==来判断是否相等,计算机的所有小数都是不精确的,我们只要确定误差小于精确值,就可以认为是相等的。
  • 考虑一些特殊情况,情况优化很多没必要的运算。
  • a&1这个与运算,其实是对2取余。
  • a>>1这个位运算,其实是除2。
  • 位运算在实际程序中的效率要高于普通的数学运算,因为普通的数学运算的基本原理还是位运算。
  • 思维,完善起来吧!!!
    数值的整数次方--一道题培养你的严密思维_第6张图片

ATFWUS --Writing By 2020–03–27

你可能感兴趣的:(算法,算法面试题集)