题目描述
剑指 Offer 16. 数值的整数次方
实现 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
-2^31 <= n <= 2^31-1
-104 <= x^n <= 104
解题分析
实现幂运算,其实最直接的思路就直接连着乘
ans = x;
while(i<n){
i++;
ans = ans*x;
}
但是这样显然会有一个问题:注意看题目下方的提示 -2^31 <= n <= 2^31-1
那么当n可能是一个非常大的数字时,采用O(n) 时间复杂度的算法必然会很大
那么有没有更优化的解决方法呢?
就,比如说我 n 这里给的很大,例如 2^64 , 那么我们其实可以发现,这个求解的过程中有很多值我们没有利用起来,2^64 = 2^32 2^32 ; 2^32 = 2^16 2^16 ……. 2^2 = 2*2 那么就可以以一种树状的递归求解方式来求得我们的答案,这种类似树状空间的复杂度为O(logn)级别。
但是这是一种理想的情况,每次都能很好的划分,然后重复利用,那么遇到一个不那么特殊的数怎么办呢?
假设当前数字为 63 ,对吧,你现在一开始就不能直接除二,
那我可以这样:63 = 31+31+1 对吧,就找个比我小但是能二分的数字,然后再乘上一个或者若干个x
那么这种算法的思想其实可以概括为分治法,那么如何具体将这个算法思想转变为代码呢?
lets try! 首先尝试一下递归解法
见3.2
ok,尝试成功,然后去学习一下最优解
由于递归需要使用额外的栈空间,我们试着将递归转写为迭代。
参考来源:https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/solution/shu-zhi-de-zheng-shu-ci-fang-by-leetcode-yoqr/
其实就是二进制表示的思想,一个数字转化为二进制后的位数上的1 就可以表示这个数字是如何由2的各个多次幂叠加得到,这也就提供了一种拆分计算的快速指示。
可以快速的根据n的二进制数上的1来看出,由低到高,哪个等级的x^(2^k)贡献了,需要我们去乘起来
代码实现
笨方法,直接循环
class Solution {
public:
double myPow(double x, int n) {
double ans = x;
int i=1;
bool flag = true;
if(n==0) return 1;
if(x==1) return 1;
if(x==0) return 0;
if(n<0) {
flag = false;
n = -n;
}
while(i<n){
i++;
ans = ans*x;
}
if(flag){
return ans;
}else{
ans = 1/ans;
return ans;
}
}
};
递归+矩阵快速幂
class Solution {
public:
double myPow(double x, int n) {
//先写递归终止条件
bool flag = true;
if(n==0) return 1;
else if(n==1) return x;
else if(n<0){
flag = false;
n = -n;
}
//再写递归规则
double ans = x;
int new_n = n/2;
int rest = n-2*new_n;
double tmp_ans = myPow(x, new_n);
ans = tmp_ans*tmp_ans*myPow(x, rest);
if(flag) return ans;
else return 1/ans;
//好,目前为止这些处理的情况都是n>0,要是n<0怎么办呢?
}
};
好吧,有些边界条件要考虑一下:
当 INT_MIN 出现时,n = -n 这个操作会导致 n 变号后直接溢出,那只能特殊处理一下了
class Solution {
public:
double myPow(double x, int n) {
//先写递归终止条件
bool flag = true;
if(n==0) return 1;
else if(n==1) return x;
else if(n<0&&n!=INT_MIN){
flag = false;
n = -n;
}else if(n==INT_MIN){
n = INT_MAX;
double ans = x;
int new_n = n/2;
int rest = n-2*new_n+1;
double tmp_ans = myPow(x, new_n);
ans = tmp_ans*tmp_ans*myPow(x, rest);
return 1/ans;
}
//再写递归规则
double ans = x;
int new_n = n/2;
int rest = n-2*new_n;
double tmp_ans = myPow(x, new_n);
ans = tmp_ans*tmp_ans*myPow(x, rest);
if(flag) return ans;
else return 1/ans;
}
};
官方最快方法(迭代+矩阵快速幂)
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);
}
};