力扣每日打卡 50. Pow(x, n) (中等)

@[TOC](力扣 50. Pow(x, n) 中等)


前言

这是刷算法题的第十一天,用到的语言是JS

题目:力扣 50. Pow(x, n) (中等)


一、题目内容

实现 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
  • n 是一个整数
  • 要么 x 不为零,要么 n > 0 。
  • -104 <= xn <= 104

二、解题方法

1. 快速幂运算(使用除模运算)

正常运算

代码如下(实例):

复制代码
/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function (x, n) {

  // 有负数,先处理特殊情况
  // 如果指数是负数,则 将底数取倒数,并且指数变正
  // 即  x^(-n) = 1 / (x^n) = (1/x)^n
  if (n === 0) return 1.00000
  if (n < 0) {
    x = 1 / x
    n = -n
  }

  let ans = 1

  if (-100.0 > x || x > 100.0) return 0.00000
  if (-(2 ** 31) > n || n > (2 ** 31)) return 0.00000
  if (x === 0 && n > 0) return 0.00000

  while (n) {
    if (n % 2 === 1) ans *= x
    x *= x
    n = Math.floor(n / 2)
  }
  return ans
};

2. 快速幂运算(使用按位与运算)

有坑:具体表现为指数右移时的判断
n >>= 1n >>>= 1 的区别:下面会讲

  • 进行无符号右移1位,此处不能使用有符号右移(>>)
  • 当n为-2^31转换成正数时的二进制位"10000000000000000000000000000000" , 如果采用有符号右移时会取最左侧的数当符号即(1),所以返回的结果是 -1073741824

代码如下(实例):

复制代码
/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function (x, n) {

  // 有负数,先处理特殊情况
  // 如果指数是负数,则 将底数取倒数,并且指数变正
  // 即  x^(-n) = 1 / (x^n) = (1/x)^n
  if (n === 0) return 1.00000
  if (n < 0) {
    x = 1 / x
    n = -n
  }

  let ans = 1

  if (-100.0 > x || x > 100.0) return 0.00000
  if (-(2 ** 31) > n || n > (2 ** 31)) return 0.00000
  if (x === 0 && n > 0) return 0.00000

  // 错误用法
  // while (n) {
  //   if (n & 1) ans *= x
  //   x *= x
  //   n >>= 1 // 符号右移
  // }
  
  // 正确用法
  while (n) {
    if (n & 1) ans *= x
    x *= x
    n >>>= 1 // 无符号右移
    //进行无符号右移1位,此处不能使用有符号右移(>>)
    //当n为-2^31转换成正数时的二进制位"10000000000000000000000000000000" , 如果采用有符号右移时会取最左侧的数当符号即(1),所以返回的结果是 -1073741824
  }
  
  return ans
};

3. 官方题解

3.1 前言

本题的方法被称为「快速幂算法」,有递归和迭代两个版本。这篇题解会从递归版本的开始讲起,再逐步引出迭代的版本。

当指数 n 为负数时,我们可以计算 x−n 再取倒数得到结果,因此我们只需要考虑 n 为自然数的情况。

3.2 方法一:快速幂 + 递归

「快速幂算法」的本质是分治算法。举个例子,如果我们要计算 x64,我们可以按照:
x → x 2 → x 4 → x 8 → x 16 → x 32 → x 64 x→x^2 →x^4 →x^8 →x^{16} →x^{32} →x^{64} x→x2→x4→x8→x16→x32→x64

的顺序,从 x x x 开始,每次直接把上一次的结果进行平方,计算 6 次就可以得到 x 64 x^{64} x64 的值,而不需要对 x x x 乘 63 次 x x x。

再举一个例子,如果我们要计算 x 77 x^{77} x77 ,我们可以按照:
x → x 2 → x 4 → x 9 → x 19 → x 38 → x 77 x→x^2 →x^4 →x^9 →x^{19} →x^{38} →x^{77} x→x2→x4→x9→x19→x38→x77

的顺序,在 x → x 2 , x 2 → x 4 , x 19 → x 38 x→x^2,x^2→x^4,x^{19}→x^{38} x→x2,x2→x4,x19→x38 这些步骤中,我们直接把上一次的结果进行平方,而在 x 4 → x 9 , x 9 → x 38 , x 38 → x 77 x^4 →x^9, x^{9} →x^{38}, x^{38} →x^{77} x4→x9,x9→x38,x38→x77 这些步骤中,我们把上一次的结果进行平方后,还要额外乘一个 x x x。

直接从左到右进行推导看上去很困难,因为在每一步中,我们不知道在将上一次的结果平方之后,还需不需要额外乘 x。但如果我们从右往左看,分治的思想就十分明显了:

  • 当我们要计算 x n x^n xn时,我们可以先递归地计算出 y = x ⌊ n / 2 ⌋ y=x^{⌊n/2⌋} y=x⌊n/2⌋ ,其中 ⌊ a ⌋ ⌊a⌋ ⌊a⌋ 表示对 a 进行下取整;

  • 根据递归计算的结果,如果 n 为偶数,那么 x n = y 2 x^n = y^2 xn=y2;如果 n 为奇数,那么 x n = y 2 x x^n = y^2x xn=y2x

  • 递归的边界为 n = 0 n=0 n=0,任意数的 0 次方均为 1。

由于每次递归都会使得指数减少一半,因此递归的层数为 O ( l o g n ) O(log n) O(logn),算法可以在很快的时间内得到结果。

复制代码
// C++
class Solution {
public:
    double quickMul(double x, long long N) {
        if (N == 0) {
            return 1.0;
        }
        double y = quickMul(x, N / 2);
        return N % 2 == 0 ? y * y : y * y * x;
    }

    double myPow(double x, int n) {
        long long N = n;
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/powx-n/solutions/238559/powx-n-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

:
时间复杂度: O ( l o g n ) O(logn) O(logn),即为递归的层数。
:
空间复杂度: O ( l o g n ) O(logn) O(logn),即为递归的层数。这是由于递归的函数调用会使用栈空间。

链接:力扣本题官方题解

来源:力扣(LeetCode)

3.3 方法二:快速幂 + 迭代

由于递归需要使用额外的栈空间,我们试着将递归转写为迭代。在方法一中,我们也提到过,从左到右进行推导是不容易的,因为我们不知道是否需要额外乘 x。但我们不妨找一找规律,看看哪些地方额外乘了 x x x,并且它们对答案产生了什么影响。

我们还是以 x 77 x^{77} x77作为例子: x → x 2 → x 4 → x 9 → x 19 → x 38 → x 77 x→x^2 →x^4 →x^9 →x^{19} →x^{38} →x^{77} x→x2→x4→x9→x19→x38→x77 并且把需要额外乘 x x x 的步骤打上了 + + + 标记。可以发现:

  • x 38 → + x 77 x^{38}→^+ x^{77} x38→+x77中额外乘的 x x x 在 x 77 x^{77} x77中贡献了 x x x;
  • x 9 → + x 19 x^{9}→^+ x^{19} x9→+x19中额外乘的 x x x 在之后被平方了 2 2 2次,因此在 x 77 x^{77} x77中贡献了 x 2 2 = x 4 x^{2^2} = x^4 x22=x4;
  • x 4 → + x 9 x^{4}→^+ x^{9} x4→+x9中额外乘的 x x x 在之后被平方了 3 3 3次,因此在 x 77 x^{77} x77中贡献了 x 2 6 = x 64 x^{2^6} = x^{64} x26=x64;

下面的代码给出了详细的注释:

复制代码
// C++
class Solution {
public:
    double quickMul(double x, long long N) {
        double ans = 1.0;
        // 贡献的初始值为 x
        double x_contribute = x;
        // 在对 N 进行二进制拆分的同时计算答案
        while (N > 0) {
            if (N % 2 == 1) {
                // 如果 N 二进制表示的最低位为 1,那么需要计入贡献
                ans *= x_contribute;
            }
            // 将贡献不断地平方
            x_contribute *= x_contribute;
            // 舍弃 N 二进制表示的最低位,这样我们每次只要判断最低位即可
            N /= 2;
        }
        return ans;
    }

    double myPow(double x, int n) {
        long long N = n;
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/powx-n/solutions/238559/powx-n-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

:
时间复杂度: O ( l o g n ) O(logn) O(logn),即为对 n 进行二进制拆分的时间复杂度。
:
空间复杂度: O ( 1 ) O(1) O(1)。

链接:力扣本题官方题解

来源:力扣(LeetCode)


三、符号右移" >>= " 与 无符号右移" >>>= " 的区别

在 JavaScript 中,>>= 是一种位移赋值运算符,表示将一个数向右位移指定的位数,并将结果赋值给该数。>>> 是无符号右位移运算符,它会将符号位(最高位)视为 0,因此用于处理负数时会有不同的结果。

1. 符号右移(>>)与无符号右移(>>>)的区别

  • >> : 符号右移运算符,它会保持符号位(最高位)不变,对于负数来说,会用 1 填充空位。例如,-811111111111111111111111111111000)右移 1 位会变成 -411111111111111111111111111111100)。

  • >>> : 无符号右移运算符,会用 0 填充空位,无论原数是负数还是正数。这导致在右移负数时,结果会是一个大的正数。例如,-8 的无符号右移 1 位,结果将是 214748364400111111111111111111111111111100)。

2. >>= 的用途

>>= 是将右移操作与赋值结合的简写形式。例如:

复制代码
let x = 8; // 二进制表示为 1000
x >>= 1;   // 向右位移 1 位
console.log(x); // 输出: 4,二进制为 0100

let y = -8; // 二进制表示为 11111111111111111111111111111000 (32位)
y >>= 1;    // 向右位移 1 位
console.log(y); // 输出: -4,二进制为 11111111111111111111111111111100

而对于无符号右移的示例:

复制代码
let z = -8; // 二进制表示为 11111111111111111111111111111000 (32位)
z >>>= 1;   // 无符号右位移 1 位
console.log(z); // 输出: 2147483644,二进制为 00111111111111111111111111111100

3. 总结

  • >>= 是一种位移赋值操作符,用于将数向右移位并赋值。
  • 适用于进行位运算时,尤其在处理与二进制数字有关的算法(如加密、压缩等)时非常有用。
  • 区别于符号右移 >>,无符号右移 >>> 不保留符号,可以否定负数的符号位。

理解这些运算符对理解 JavaScript 的位运算有非常重要的作用,尤其在需要直接操作底层二进制数据时。


四、可以用>>>= 永久性代替>>=吗

在 JavaScript 中,>>=>>>= 的作用虽然相似,但它们无法彼此完全替代,因为它们处理符号的方式不同。

下面是详细的解释:

1. 何时使用 >>=>>>=

  • 如果你需要保持负数的符号,并且希望结果也为负数,应该使用 >>=
  • 如果你希望对数字进行无符号右移,且希望即使是负数也得到正数结果,应该使用 >>>=

2. 结论

  • 不能用 >>>= 永久性代替 >>=,因为它们的算法及结果不同。
  • 根据所需的数值结果来选择合适的操作符:
    • 使用 >>= 时结果保持符号(适用于有符号整数)。
    • 使用 >>>= 时结果不保持符号(适用于无符号整数)。

因此,选择 >>= 还是 >>>= 取决于你的具体需求和你希望如何处理符号。

相关推荐
—Qeyser34 分钟前
用 Deepseek 写的uniapp血型遗传查询工具
前端·javascript·ai·chatgpt·uni-app·deepseek
codingandsleeping35 分钟前
HTTP1.0、1.1、2.0 的区别
前端·网络协议·http
小满blue37 分钟前
uniapp实现目录树效果,异步加载数据
前端·uni-app
爱数模的小驴2 小时前
2025 年“认证杯”数学中国数学建模网络挑战赛 C题 化工厂生产流程的预测和控制
深度学习·算法·计算机视觉
喜樂的CC2 小时前
[react]Next.js之自适应布局和高清屏幕适配解决方案
javascript·react.js·postcss
天天扭码2 小时前
零基础 | 入门前端必备技巧——使用 DOM 操作插入 HTML 元素
前端·javascript·dom
咖啡虫3 小时前
css中的3d使用:深入理解 CSS Perspective 与 Transform-Style
前端·css·3d
烛阴3 小时前
手把手教你搭建 Express 日志系统,告别线上事故!
javascript·后端·express
拉不动的猪3 小时前
设计模式之------策略模式
前端·javascript·面试
旭久3 小时前
react+Tesseract.js实现前端拍照获取/选择文件等文字识别OCR
前端·javascript·react.js