一道简单的求幂次题Pow(x, n)引发的思考

72 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情

50. Pow(x, n)

拿到题目就感觉非常简单,不就是一直相乘么,然后就写出了如下代码:

var myPow = function(x, n) {
    let res = 1
    while (n) {
        res *= x
        n -= 1
    }
    return n >= 0 ? res : 1/res
};

提及后:emmmm image.png

暴力法时间复杂度为: O(n),空间复杂度为:O(1)

因为是时间复杂度超了,那我们就要开始想,怎么才能少乘几次呢。就想到了分治。

  • 比如Pow(2, 8),就等于Pow(4, 4),Pow(2, 9),就等于Pow(4, 4) * 2
  • 那么,首先需要进行奇偶判断,如果是偶数,直接求pow(xx, n/2),如果是奇数,那么n-1一定是偶数,就相当于求pow(xx,(n-1)/2)*x
  • 那么代码如下
var myPow = function(x, n) {
    if (n === 0) {
        return 1
    }
    let a = n > 0 ? n : -n
    // 判断奇偶
    if (n & 1 === 1) {
        return n > 0 ? myPow(x * x, (a-1)/2) * x : 1/(myPow(x * x, (a-1)/2) * x)
    }
    return n > 0 ? myPow(x * x, a / 2) : 1 / myPow(x * x, a / 2)
};

时间复杂度为:O(logn),空间复杂度为:O(logn)(递归调用栈使用)

image.png 但是不知道为什么这种写法,看着有些恶心,主要是对负数的判断那一块,于是想到负数的求解,只是比正数多一步,代码如下:

var myPow = function(x, n) {
    if (n === 0) {
        return 1
    }
    if (n < 0) {
        return 1 / myPow(x, -n)
    }
    // 判断奇偶
    if (n & 1 === 1) {
        return myPow(x * x, (n-1)/2) * x
    }
    return myPow(x * x, n / 2)
};

image.png

做到这一步,看起来已经不错了,但是难道没有更好的方案吗?

答案一点是有的,就在上章提到的位运算,一般来说,用位运算的解答方案会比其他方案要快。

  • 将n转为x进制,在n为1的位,结果乘上这个值
  • 每次n往右移一次,x即为之前值的平方
  • 要注意,右移应该是无符号右移,如果是有符号,对于负数会移成一个奇奇怪怪的输
var myPow = function(x, n) {
    if (n === 0) {
        return 1
    }
    if (n < 0) {
        x = 1 / x
        n = -n
    }
    let res = 1
    while (n) {
        if (n & 1) res *= x
        x *= x
        n >>>= 1
    }
    return res
};

image.png

时间复杂度为O(logn),空间复杂度位:O(1),大大优化了空间

虽然看起来时间复杂度好像更低了,但leetcode计算出的时间复杂度和执行环境/网络等都有关系,所以并不准确