力扣50号算法题Pow(x, n)。快速幂的递归与位运算实现及扩展题372. 超级次方

问题:力扣50. Pow(x, n)

解法一: 直接暴力for循环:

  • 就是通过for 循环 O(n) 时间复杂度,不断的进行累加, 会出现超时。

    // 暴力破解,O(n) 迭代
    func myPow(x float64, n int) float64 {
        // 如果指数小于 0,那么将底数变为对应的
        if n < 0 {
            x = 1 / x
            n = -n
        }
        var rst float64 = 1 
        for ;n > 0; n-- {
            rst *= x
        }
        return rst 
    }
    

解法二:递归实现快速幂:

  • 先来分析一波: 上面的暴力破解, O(n) 的时间复杂度,就是每次都是乘x, 那么可以将问题进行拆解:

  • 例如:

    • 3 ^ 16 == 3 ^ 8 * 3 ^ 8
    • 3 ^ 8 == 3 ^ 4 * 3 ^ 4
    • 3 ^ 4 == 3 ^ 2 * 3 ^ 2
    • 3 ^ 2 == 3 * 3
    • 类似 二分查找一样的,每次将问题范围缩小一半。那么时间复杂度就是 O(log n), n 表示指数, 上面的 16。
    • 我们使用递归实现这一过程,很像归并排序! 再递的时候,不断缩小范围,再归的过程,再去计算两两乘积,最后得到最终结果。
    func quickPow(x float64, n int) float64 {
        // 递归终止条件
        if n == 0 {
            return 1.0
        }
        // 分解 
        mid := n / 2
        // 递的过程,不断的缩小范围 16 - 8 - 4 - 2 - 1
        y := quickPow(x, mid)
        // 归的过程,就是 不断乘积
        return y * y
    }
    
  • 还需要考虑一个关键点是: 当前的值可能是 奇数, 例如

    • 3 ^ 19 == 3 ^ 9 * 3 ^ 9 * 3
    • 所以,上面的 y * y 应该进行判断, 如果是 偶数,就 y * y,如果是奇数,就需要 y * y * x 才是正确的!
  • 上最终代码:

    // 暴力破解,O(n) 迭代
    func myPow(x float64, n int) float64 {
        // 1. 快速幂计算
        var quickPow func(float64, int) float64
        quickPow = func(x float64, n int) float64 {
            // 两个递归终止条件, 必须有下面一个 0 的判断 才能对
            // if n == 1 {
            //     return x 
            // }
            if n == 0 {
                return 1.0
            }
            // 分解 
            mid := n / 2
            // 递归
            y := quickPow(x, mid)
            // 可能是 奇数,3 ^ 19 == 3 ^ 9 * 3 ^ 9 * 3
            if n % 2 == 0 {
                return y * y
            } else {
                return y * y * x
            }
        }
        
        // 2. 如果指数小于 0,那么将底数变为对应的
        if n < 0 {
            x = 1 / x
            n = -n
        }
        
        // 3. 调用快速幂进行计算
        return quickPow(x, n)
    }
    

解法三: 位运算优化实现

  • x ^ 15 , 对于指数进行分解, 拆为2进制表示

    • 15 = 1111 。 那么 要知道 二进制从低到高 每位的表示为 1, 2, 4, 8.
    • 将当前的结果就 转换为了: x ^ (2^1) * x ^ (2^2) * x ^ (2^4) * x ^ (2^8)
    • 并且每一位上是有 规律的。 将上面的四个部分表示为 y1 * y2 * y3 * y4
      • 那么 ,根据指数 1 ,2 ,4, 8的关系。两数相乘等于指数相加
      • y2 = y1 * y1
      • y3 = y2 * y2
      • y4 = y3 * y3
    • 那么我们将指数,化为 2进制形式, 按照上面的规律 求得 y1 到 y4,再将他们累乘,就是最后的结果了。
    • 下面计算 x ^ 15 次方,可以写成这样:
    // 定义结果
    rst := 1.0
    // 15 = 1111
    // 循环, 取 n代表指数, 取它的每一位。利用右移运算符。
    // 当n 还大于0,就右移一位。等于取每一位
    for n != 0 {
    	// 第一位 为 1, y1 = x ^ 1 , 等于 x 
        rst *= x
        // 到了下一位 因为 1 2 4 8, 所以下一位,等于当前 y1 * y1 == x * x
        x *= x
        // 右移一位
        n >>= 1 
    }
    // 循环这个过程, y2 = y1 * y1 。。。
    // 然后 rst 累乘每一步的结果。 每一步的结果,又是上一步 相乘。 
    // x^2 * x^2 == x ^ (2 + 2) == x ^ 4 , 对应2进制每一位 1 2 4 8
    return rst 
    
  • 由上面的结果,还有一定的 bug!, 因为我们选择的是 x ^ 15

    • 15 = 二进制 1111 , 所以每一步二进制计算的 结果 “y” 累乘起来就是最终结果。
    • 但是 如果是 x^ 9 呢? 9 == 1001(二进制). 也就是说
    • x ^ 9 == (x ^ 8) * (x ^ 0) * (x ^ 0) * (x ^ 1)
    • x ^ 0 == 1, 也就是说 ,x ^ 9 == (x ^ 8) * 1 * 1 * (x ^ 1)
    • 所以,我们要进行判断, 当,当前二进制位为 0的时候,rst 结果不能 乘以 当前的 y, 而应该跳过。因为 乘 1 等于自身。但是 y2 == y1 * y1 不变。 即使当前的 二进制位为 0, rst 不累乘,但是还要计算下一次的 y
  • 最终的代码:

    • 因为 是在 int32 范围内,所以最多取 32位,时间复杂度是 O(32),空间复杂度是O(1)。
    // 位运算最优解,  快速幂 + 迭代
    // 因为计算结果在 int32 范围内, 所以 
    // 空间复杂度为 O(1), 时间复杂度为 O(32)
    func myPow(x float64, n int) float64 {
        // 1. 如果指数小于 0,那么将底数变为对应的
        if n < 0 {
            x = 1 / x
            n = -n
        }
        rst := 1.0
        for n != 0 {
        	// 当前二进制位等于 1才累加到结果集
            if n & 1 == 1 {
                rst *= x
            }
            // 下一轮的 y 值,等于当前 y 值相乘
            x *= x
            n >>= 1 
        }
        return rst 
    }
    

问题 :力扣 372. 超级次方

  • 有了上面快速幂的基础, 我们可以看下面这道题,可以使用快速幂解答。
    在这里插入图片描述

  • 这道题 求得就是 a^b 对 1337 取模。 先需要求得 a 的 b次方,再进行 1337 取模。且 b 是个很大的数,超过32位。

    // 按照理论: https://leetcode-cn.com/problems/super-pow/solution/gong-shui-san-xie-di-gui-kuai-su-mi-ying-yx1j/ 
    // 这个大佬的讲解,只要将最后的数字转换为 
    const mod = 1337
    
    func pow(x, n int) int {
        res := 1
        for ; n > 0; n /= 2 {
            if n&1 > 0 {
                res = res * x % mod
            }
            x = x * x % mod
        }
        return res
    }
    
    func superPow(a int, b []int) int {
        ans := 1
        for i := len(b)-1; i >= 0; i-- {
            ans = ans * pow(a, b[i]) % mod
            a = pow(a, 10)
        }
        return ans
    }
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值