「力扣」第 50 题:Pow(x, n)

720 阅读1分钟

今天要和大家分享的是「力扣」第 50 题:Pow(x, n)。这题有一个名称叫「快速幂」,我们这里只分享「递归」和「非递归」的写法,其中 「递归」对应「当指数为奇数时,把指数分解成偶数 + 1,当指数为偶数时,把指数除以 2」,「非递归」对应把指数转化成二进制。「快速幂」还有矩阵的求法,感兴趣的朋友可以在网络上自行搜索(我也不会)。

下面说正事。

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

提示:

  • 100.0<x<100.0-100.0 < x < 100.0
  • 231n2311-2^{31} \le n \le 2^{31}-1
  • 104xn104-10^4 \le x^n \le 10^4

方法一:递归(分治算法)

这个思路关键的地方在于:不断降低指数

例如:27=26+12^7 = 2^{6+1} ​,为什么要把 11​ 单独拿出来呢,这是因为 指数为偶数的时可以让底数变大,而指数变成原来的一半26=22×3=(22)32^6 = 2^{2\times 3} = (2^2)^3​。把递归树画出来就是下面这个样子,到指数为 00​ 或者为 11​​ 的时候递归终止。

拆分的规律如下:

  • 如果指数为奇数,拆成偶数 + 1;
  • 如果指数为偶数,拆成指数除以 2;

下面画一个递归树理解拆分的过程。

程序在计算的时候,先不停地让指数降低,然后再一层一层向上传递结果的方式计算出 xnx^n,大家可以自行想象程序是如何计算结果的。

参考代码 1

public class Solution {

    public double myPow(double x, int n) {
        long N = n;
        if (N < 0) {
            return 1 / dfs(x, -N);
        }
        return dfs(x, N);
    }

    private double dfs(double x, long n) {
        if (n == 0) {
            return 1;
        }
      
      	// n % 2 == 1 包含了 n = 1 这种情况
        if (n % 2 == 1) {
            return x * dfs(x, n - 1);
        }
        return dfs(x * x, n / 2);
    }
}

说明

  • 当指数 n 为负数的时候,xn=x1×(n)=(x1)n=(1x)n=1xnx^n = x^{-1 \times (-n)} = (x^{-1})^{-n} = (\frac{1}{x})^{-n} = \frac{1}{x^{-n}}

  • 由于 n 有可能等于 231-2^{31}​,因此一开始的时候需要先把 n 转换成长整型;

  • 递归函数如果实在没有什么好的名字,叫 dfs 是比较通用的,这里其实叫 pow 也可以,只是为了和主调用函数区分(虽然 Java 有重载),所以叫另外一个名字。

时间复杂度O(logn)O(\log n)O(log2n)=O(logn)O(\log_2 n) = O(\log n)​​。

如果把递归解法看成「自顶向下」解决问题,其实这个问题还可以「自底向上」地解决。

想法是:从 x1x^1​ 开始,依次计算 x2x^2​、x4x^4​、x8x^8​、x16x^{16}​ ……,让指数成倍增加。在自增的过程中,如果 xnx^n​ 需要其中的数,把它乘到结果集中。因此拆分的规律是:把指数拆成 22​​ 的方幂的和。例如:把 1818​ 看成 16+216 + 2​​。 这个过程其实就是把一个十进制整数转化为二进制的过程。

方法二:把指数转换成二进制

例如计算 x18x^{18}​​,其中 1818​​ 可以看成:

18=1×24+0×23+0×22+1×21+0×2018=1 \times 2^4 + 0 \times 2^3 + 0 \times 2^2 +1 \times 2^1 + 0 \times 2^0

于是 x18=x24×x21x^{18} = x^{2^4} \times x^{2^1}​,22​​ 的方幂前的系数是我们需要知道的,把十进制转换为二进制我们都很熟悉了,不断除以 2,看余数

下面的「参考代码 2」就是上面的运算的模拟。

参考代码 2

public class Solution {

    public double myPow(double x, int n) {
        long N = n;
        if (n < 0) {
            x = 1 / x;
            N = -N;
        }

        double res = 1.0;
        while (N > 0) {
            if (N % 2 == 1) {
                res *= x;
            }
            x *= x;
            N /= 2;
        }
        return res;
    }
}

时间复杂度O(logn)O(\log n)O(log2n)=O(logn)O(\log_2 n) = O(\log n)

这就是今天要和大家分享的内容,感谢大家的收看。