八叶一刀·无仞剑

万物流转,无中生有,有归于无

导航

数值的整数次方

Posted on 2021-01-24 17:45  闪之剑圣  阅读(124)  评论(0编辑  收藏  举报

这是《剑指offer》里的一道题,内容是给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
多年没怎么刷过题的我,第一眼感觉就是这题用最简单的乘法做下去肯定是对的,但可能效率堪忧。当然要考虑一些特殊情况,比如是exponent是0或负数,base是0或1之类的:

def pow(base, exponent):
    if base in (0.0, 1.0):
        return base
    if exponent == 0:
        return 1.0
    num = 1.0
    for i in xrange(abs(exponent)):
        num *= base
    return num if exponent > 0 else 1.0/num

其实这道题的代码是可以优化的。可以想象一下,假设我们要求\(a^9\)。我们可以根据二进制将9进行分解,9=42+1,那么如果我们能算出\(a^4\),就可以反推出\(a^9\)了。同理,我们要算\(a^4\),因为4=22,因此只要我们知道\(a^2\),就可以算出\(a^4\)了。由a推出\(a^2\)则很明显。那么实际上的计算量就是\(a^9 = a^4 * a^4 * a, a^4=a^2 * a^2, a^2 = a * a\),一共是4次乘法。这比直接算9次a的乘法可简单太多了。
其实有了以上思路,不难写出一个递归的算法。不过这里我打算写一个非递归的做法。基本思路是:对exponent一边除2一边入栈,如果当前的值是奇数的话入栈的数为1,否则为0,一直除到exponent为0。然后再出栈反推回\(base^{exponent}\),根据出栈的值为0或1决定是否要多乘一次a,最终可反推出结果。
具体的运行过程我画了幅图:

def pow(base, exponent):
    if base in (0.0, 1.0):
        return base
    if exponent == 0:
        return 1.0
    num = 1.0
    stack = []
    abs_exponent = abs(exponent)
    while abs_exponent:
        stack.append(1 if abs_exponent % 2 else 0)
        abs_exponent = abs_exponent >> 1
    
    for i in xrange(len(stack)-1,-1,-1):
        num = num * num
        if stack[i]:
            num *= base
    return num if exponent > 0 else 1.0/num

通过这种方法就可以将一个原本O(n)的算法优化为O(logN)。这也告诉我们:在写代码时可以考虑一下能否重用或递推已有的计算结果,这样往往可以使得代码运行效率更高。