LCViewer

vuePress-theme-reco LiuYao    2019 - 2021
LCViewer LCViewer

Choose mode

  • dark
  • auto
  • light
主页
分类
  • 中等
  • 困难
  • 简单
标签
时间轴
联系
  • GitHub
  • 博客

LiuYao

341

文章

56

标签

主页
分类
  • 中等
  • 困难
  • 简单
标签
时间轴
联系
  • GitHub
  • 博客
  • 剑指 Offer 16-数值的整数次方(数值的整数次方 LCOF)

    • 中文题目
      • 通过代码
        • 高赞题解
          • 提交历史
            • 统计信息

            剑指 Offer 16-数值的整数次方(数值的整数次方 LCOF)

            vuePress-theme-reco LiuYao    2019 - 2021

            剑指 Offer 16-数值的整数次方(数值的整数次方 LCOF)


            LiuYao 2021-10-18 递归<Recursion> 数学<Math>

            # 中文题目

            实现 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
            • -231 <= n <= 231-1
            • -104 <= xn <= 104

            注意:本题与主站 50 题相同:https://leetcode-cn.com/problems/powx-n/

            # 通过代码

            class Solution {
                public double myPow(double x, int n) {
                    if(n == 0){
                        return 1.0;
                    }
                    int m = 0;
                    if(n < 0){
                        m = -(n/2);
                        x = 1.0 /x;
                    }else{
                        m = n/2;
                    }
                    return (n % 2 == 0) ? Math.pow(x*x, m) :x*Math.pow(x*x, m); 
                }
            }
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            class Solution {
                public double myPow(double x, int n) {
                    if(n == 0){
                        return 1.0;
                    }
                    int m = 0;
                    if(n < 0){
                        m = -(n/2);
                        x = 1.0 /x;
                    }else{
                        m = n/2;
                    }
                    return (n % 2 == 0) ? Math.pow(x*x, m) :x*Math.pow(x*x, m); 
                }
            }
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            class Solution {
                public double myPow(double x, int n) {
                    // 如果x==0,直接返回0,避免后面1/0报错
                    if (x == 0) {
                        return 0;
                    }
                    // 乘方次数,注意这里要用long型,因为-2147483648的正数+2147483648没办法用int表示,需要使用long
                    long b = n;
                    // 结果值
                    double res = 1.0;
                    // 如果乘方次数小于0,比如2^-2  可以转换为(1/2)^2
                    if (b < 0) {
                        // 底数取倒
                        x = 1 / x;
                        // 指数取正
                        b = -b;
                    }
                    // 当指数大于0时
                    while (b > 0) {
                        // 如果当前指数为奇数的话,将多出一项x,乘入res
                        // 最后都会变成 x^n=x^0*res=res
                        // 当 b为1时,就会将之前的结果都乘入到res中
                        if ((b & 1) == 1)
                            res *= x;
                        x *= x;
                        b >>= 1;
                    }
                    return res;
                }
            }
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29

            # 高赞题解

            # 解题思路:

            求 xnx^nxn 最简单的方法是通过循环将 nnn 个 xxx 乘起来,依次求 x1,x2,...,xn−1,xnx^1, x^2, ..., x^{n-1}, x^nx1,x2,...,xn−1,xn ,时间复杂度为 O(n)O(n)O(n) 。 快速幂法 可将时间复杂度降低至 O(log2n)O(log_2 n)O(log2​n) ,以下从 “二分法” 和 “二进制” 两个角度解析快速幂法。

            # 快速幂解析(二进制角度):

            利用十进制数字 nnn 的二进制表示,可对快速幂进行数学化解释。

            • 对于任何十进制正整数 nnn ,设其二进制为 "bm...b3b2b1b_m...b_3b_2b_1bm​...b3​b2​b1​"( bib_ibi​ 为二进制某位值,i∈[1,m]i \in [1,m]i∈[1,m] ),则有:

              • 二进制转十进制: n=1b1+2b2+4b3+...+2m−1bmn = 1b_1 + 2b_2 + 4b_3 + ... + 2^{m-1}b_mn=1b1​+2b2​+4b3​+...+2m−1bm​ (即二进制转十进制公式) ;
              • 幂的二进制展开: xn=x1b1+2b2+4b3+...+2m−1bm=x1b1x2b2x4b3...x2m−1bmx^n = x^{1b_1 + 2b_2 + 4b_3 + ... + 2^{m-1}b_m} = x^{1b_1}x^{2b_2}x^{4b_3}...x^{2^{m-1}b_m}xn=x1b1​+2b2​+4b3​+...+2m−1bm​=x1b1​x2b2​x4b3​...x2m−1bm​ ;
            • 根据以上推导,可把计算 xnx^nxn 转化为解决以下两个问题:

              • 计算 x1,x2,x4,...,x2m−1x^1, x^2, x^4, ..., x^{2^{m-1}}x1,x2,x4,...,x2m−1 的值: 循环赋值操作 x=x2x = x^2x=x2 即可;
              • 获取二进制各位 b1,b2,b3,...,bmb_1, b_2, b_3, ..., b_mb1​,b2​,b3​,...,bm​ 的值: 循环执行以下操作即可。
                1. n&1n \& 1n&1 (与操作): 判断 nnn 二进制最右一位是否为 111 ;
                2. n>>1n>>1n>>1 (移位操作): nnn 右移一位(可理解为删除最后一位)。
            • 因此,应用以上操作,可在循环中依次计算 x20b1,x21b2,...,x2m−1bmx^{2^{0}b_1}, x^{2^{1}b_2}, ..., x^{2^{m-1}b_m}x20b1​,x21b2​,...,x2m−1bm​ 的值,并将所有 x2i−1bix^{2^{i-1}b_i}x2i−1bi​ 累计相乘即可。

              • 当 bi=0b_i = 0bi​=0 时:x2i−1bi=1x^{2^{i-1}b_i} = 1x2i−1bi​=1 ;
              • 当 bi=1b_i = 1bi​=1 时:x2i−1bi=x2i−1x^{2^{i-1}b_i} = x^{2^{i-1}}x2i−1bi​=x2i−1 ;

            Picture1.png{:width=450}

            # 快速幂解析(二分法角度):

            快速幂实际上是二分思想的一种应用。

            • 二分推导: xn=xn/2×xn/2=(x2)n/2x^n = x^{n/2} \times x^{n/2} = (x^2)^{n/2}xn=xn/2×xn/2=(x2)n/2 ,令 n/2n/2n/2 为整数,则需要分为奇偶两种情况(设向下取整除法符号为 "//////" ):

              • 当 nnn 为偶数: xn=(x2)n//2x^n = (x^2)^{n//2}xn=(x2)n//2 ;
              • 当 nnn 为奇数: xn=x(x2)n//2x^n = x(x^2)^{n//2}xn=x(x2)n//2 ,即会多出一项 xxx ;
            • 幂结果获取:

              • 根据二分推导,可通过循环 x=x2x = x^2x=x2 操作,每次把幂从 nnn 降至 n//2n//2n//2 ,直至将幂降为 000 ;
              • 设 res=1res=1res=1 ,则初始状态 xn=xn×resx^n = x^n \times resxn=xn×res 。在循环二分时,每当 nnn 为奇数时,将多出的一项 xxx 乘入 resresres ,则最终可化至 xn=x0×res=resx^n = x^0 \times res = resxn=x0×res=res ,返回 resresres 即可。

            Picture2.png{:width=450}

            • 转化为位运算:
              • 向下整除 n//2n // 2n//2 等价于 右移一位 n>>1n >> 1n>>1 ;
              • 取余数 n%2n \% 2n%2 等价于 判断二进制最右一位值 n&1n \& 1n&1 ;
            # 算法流程:
            1. 当 x=0x = 0x=0 时:直接返回 000 (避免后续 x=1/xx = 1 / xx=1/x 操作报错)。
            2. 初始化 res=1res = 1res=1 ;
            3. 当 n<0n < 0n<0 时:把问题转化至 n≥0n \geq 0n≥0 的范围内,即执行 x=1/xx = 1/xx=1/x ,n=−nn = - nn=−n ;
            4. 循环计算:当 n=0n = 0n=0 时跳出;
              1. 当 n&1=1n \& 1 = 1n&1=1 时:将当前 xxx 乘入 resresres (即 res∗=xres *= xres∗=x );
              2. 执行 x=x2x = x^2x=x2 (即 x∗=xx *= xx∗=x );
              3. 执行 nnn 右移一位(即 n>>=1n >>= 1n>>=1)。
            5. 返回 resresres 。
            # 复杂度分析:
            • 时间复杂度 O(log2n)O(log_2 n)O(log2​n) : 二分的时间复杂度为对数级别。
            • 空间复杂度 O(1)O(1)O(1) : resresres, bbb 等变量占用常数大小额外空间。

            # 代码:

            Java 代码中 int32 变量 n∈[−2147483648,2147483647]n \in [-2147483648, 2147483647]n∈[−2147483648,2147483647] ,因此当 n=−2147483648n = -2147483648n=−2147483648 时执行 n=−nn = -nn=−n 会因越界而赋值出错。解决方法是先将 nnn 存入 long 变量 bbb ,后面用 bbb 操作即可。

            class Solution:
                def myPow(self, x: float, n: int) -> float:
                    if x == 0: return 0
                    res = 1
                    if n < 0: x, n = 1 / x, -n
                    while n:
                        if n & 1: res *= x
                        x *= x
                        n >>= 1
                    return res
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            class Solution {
                public double myPow(double x, int n) {
                    if(x == 0) return 0;
                    long b = n;
                    double res = 1.0;
                    if(b < 0) {
                        x = 1 / x;
                        b = -b;
                    }
                    while(b > 0) {
                        if((b & 1) == 1) res *= x;
                        x *= x;
                        b >>= 1;
                    }
                    return res;
                }
            }
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17

            # 提交历史

            提交时间 提交结果 标记状态 我的注释 执行时间 战胜比例 内存消耗 语言
            2021-10-18 22:13:10 Accepted 🚩🚩 注意-n 的整数溢出 0 ms 100.0% 37.9 MB java
            2021-10-18 22:11:53 Wrong Answer N/A N/A N/A java
            2021-10-18 22:11:34 Wrong Answer N/A N/A N/A java
            2021-10-18 21:49:00 Runtime Error N/A N/A N/A java
            2021-10-18 21:47:45 Wrong Answer N/A N/A N/A java
            2021-06-16 23:03:29 Accepted 🚩 递归的方式计算快速幂 1 ms 100.0% 37.7 MB java
            2021-06-16 23:01:11 Accepted 🚩 通过快速幂,每次乘平方,奇数多乘一个 1 ms 100.0% 37.8 MB java

            # 统计信息

            通过次数 提交次数 AC比率
            126518 372063 34.0%