1. 题目描述
2. 解题思路
上一次看见这种类型的题目还是在上一次…例如这个【LeetCode - Java】69. Sqrt(x) (简单),或者更离谱的这个【LeetCode - Java】29. 两数相除(中等)| 地狱难度。于是乎在这里我先尝试了使用牛顿迭代法去逼近,发现这道题若使用牛顿迭代法的目标答案只能转化为 l o g ( a n s ) log(ans) log(ans) ,显然答案就不能为负数了,因此牛顿迭代法无法解决这一道题目,然后我就转向了位运算的思路。
在“两数相除”那道题目中,是把除数不断地分次向左位移实现模拟乘二的效果,最后相加得出商,其实是属于一种分治的思想。下面我们来观察一下幂函数计算可以分解为什么,对于 5 3 5^3 53,它可以分解为 5 2 ∗ 5 1 5^2 * 5^1 52∗51,那么对于 5 31 5^{31} 531呢?它可以分解为 5 16 + 5 8 + 5 4 + 5 2 + 5 1 5^{16}+5^8+5^4+5^2+5^1 516+58+54+52+51。这时候你会有疑问了,分解开有什么用, 它可以加快幂运算吗? 下面我们来捋一捋。
假如我们计算 5 31 5^{31} 531,我们需要算30次乘法;但如果计算 5 16 ∗ 5 8 ∗ 5 4 ∗ 5 2 ∗ 5 1 5^{16}*5^8*5^4*5^2*5^1 516∗58∗54∗52∗51呢?发现规律了吗? 5 16 5^{16} 516刚好是 5 8 5^8 58的平方,而 5 8 5^8 58也刚好是 5 4 5^4 54的平方,换言之我们只需要进行4次乘法即可得出答案了,显然这可以大大提高效率。
其实分解公式是简化了的,对于 5 31 5^{31} 531,31的二进制表示是11111,因此分解成
- 5 2 4 ∗ 5 2 3 ∗ 5 2 2 ∗ 5 2 1 ∗ 5 2 0 = 5 16 ∗ 5 8 ∗ 5 4 ∗ 5 2 ∗ 5 1 5^{2^4}*5^{2^3}*5^{2^2}*5^{2^1}*5^{2^0}=5^{16}*5^8*5^4*5^2*5^1 524∗523∗522∗521∗520=516∗58∗54∗52∗51
而如果对于 5 10 5^{10} 510,10的二进制表示是1010,那么就应该分解成
- 5 2 3 ∗ 5 0 ∗ 5 2 1 ∗ 5 0 = 5 8 ∗ 5 2 5^{2^3}*5^{0}*5^{2^1}*5^{0}=5^8*5^2 523∗50∗521∗50=58∗52
显然,如何累乘是与指数的二进制表示有关,因此在实际编程中可以先求出指数的二进制表示,再进行答案构建,也可以把指数的二进制生成与答案生成同时进行,以节省时间与空间。
另外,提醒一个有意思的点,在int
中把Integer.MIN_VALUE
转换为相反数会溢出我相信大家都是能够理解的,但是很神奇的是在代码中进行n=-n
的操作后居然是原样不变的,即对-2147483648取负的答案依然是-2147483648。我猜应该大家都猜到了这是由于原码反码补码的关系,详细我就不在这里叙述了,有兴趣可以推导一下,这也是这里代码实现中所需要注意的一个小细节。
3. 代码实现
3.1 快速幂 先求指数二进制
public double myPow(double x, int n) {
if (n == 0 || x == 1)
return 1;
if (n < 0) {
x = 1 / x;
n = -n;
}
StringBuilder sb = new StringBuilder();
while (n != 0) {
sb.append(n % 2);
n /= 2;
}
char[] chars = sb.toString().toCharArray();
System.out.println(sb);
double ans = 1;
double temp = x;
if (chars[0] == '1')
ans = x;
for (int i = 1; i < chars.length; i++) {
temp *= temp;
if (chars[i] == '1') {
System.out.println(i);
ans *= temp;
}
}
return ans;
}
3.2 快速幂 同时求指数二进制
public double myPow(double x, int n) {
if (n == 0 || x == 1)
return 1;
if (n < 0) {
x = 1 / x;
n = -n;
}
double ans = 1,temp = x;
while (n != 0) {
if (n % 2 == 1||n % 2 == -1){
ans *= temp;
}
temp *= temp;
n /= 2;
}
return ans;
}
3.3 对比
先求二进制表示的解法一共循环了2logn次,而同时求的解法一共循环了logn次,因此两种解法的时间复杂度都是O(logn),实际表现上同时求更优;而同样的,先求二进制表示的解法需要额外的空间存储二进制表示字符串,因此其空间复杂度是O(logn),而同时求不需要,空间复杂度为O(1)。