问题:力扣50. Pow(x, n)
-
题目就是 力扣: 50. Pow(x, n): https://leetcode-cn.com/problems/powx-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 }