welcome to my blog
剑指offer面试题16(java版):数值的整数次方
题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
class Solution {
public double myPow(double x, int n) {
if(x==0){
return 0;
}
if(x==1){
return 1;
}
//
if(n==0){
return 1;
}
if(n==1){
return x;
}
//
double res = 0;
if(n<0){
if(n==Integer.MIN_VALUE){
n++;
n = -n; //Integer.MAX_VALUE
x = 1/x;
return x*x * myPow(x*x, n/2);
}else{
n = -n;
x = 1/x;
}
}
return n%2==0? myPow(x*x, n/2) : x*myPow(x*x, n/2);
}
}
第四次做; 力扣上的案例比牛客上多很多, 之前在牛客写的方法不能通过了; 核心: 1) core()考虑了exponent的二进制形式, 自底向上计算幂, 这是快速幂的计算方式, 是一个通用方法; 2) core2()使用分支的思想, 自顶向下思考, 自底向上实现, 注意避免重复计算; 3) 要考虑exponent==-2147483648的情况
class Solution {
public double myPow(double x, int n) {
//看起来应该不会出现0的0次方的情况
//
if(n==0)
return 1;
if(x==0)
return 0;
//特殊案例:x=1.00000, n=2147483647; 不单独处理会发生超时
if(x==1)
return 1;
//记录次方的绝对值
int tmp=0;
//这样做会有溢出风险, 比如n原本是-2147483648, 取绝对值后发生溢出
// tmp = Math.abs(n);
if(n==Integer.MIN_VALUE)
tmp=Integer.MAX_VALUE;
else
tmp=Math.abs(n);
double cur = core(x, tmp);
if(n==Integer.MIN_VALUE)
cur = cur*x;
return n>0 ? cur : 1/cur;
}
//通过的方法
//分而治之; x^a = x^(a/2) * x^(a/2); 自顶向下思考, 自底向上实现
private double core2(double x, int n){
if(n==1)
return x;
double tmp = core(x, n/2);
return n%2==0? tmp*tmp : tmp*tmp*x;
}
//通过的方法
//使用exponent的二进制形式, 这样exponent由32位构成, 最多循环32次
private double core(double x, int n){
double res = 1;
while(n>0){
//最低位为1, 更新res
if((n&1)==1){
res = res * x;
}
//更新x
x = x*x;
//更新n
n = n>>1;
}
return res;
}
//递归函数逻辑: 当前数x乘上x的(n-1)次方的结果; 不能通过极端案例x=0.00001, n=2147483647, 栈溢出
// private double core(double x, int n){
// if(n==0)
// return 1;
// return x*core(x,n-1);
// }
//这种计算方式太耗时, 极端案例: x=2.00000 n=-2147483648, 超时
// private double core(double x, int n){
// double res = 1;
// while(n>0){
// res = res * x;
// n--;
// }
// return res;
// }
//分而治之; x^a = x^(a/2) * x^(a/2); 两个core()会存在大量重复, 超时, x=0.00001 n=2147483647
// private double core(double x, int n){
// if(n==1)
// return x;
// int mi = n%2==0? n/2 : 1+n/2;
// //两个core()会产生大量重复计算
// return core(x, mi) * core(x, n-mi);
// }
}
思路
- 按base和exponent取值分情况讨论, 具体见注释
- 当exponent为负数时,要注意将base取倒数,同时将exponent变成正数,表示base自乘exponent次
复杂度
第三次做, 按照底数是否为0分为两大类; 比较特殊的情况:底数为0; 指数小于0; 合适的分类讨论配合if else会让代码变简单
public class Solution {
public double Power(double base, int exponent) {
if(base==0){
if(exponent<=0)
throw new RuntimeException("底数不能为0");
return 0.0;
}
double temp = multiply(base, Math.abs(exponent));
return exponent >= 0 ? temp : 1/temp;
}
public double multiply(double base, int e){
int res = 1;
for(int i=0; i<e; i++)
res *= base;
return res;
}
}
第二次做 次幂问题:按照指数和底数分情况讨论
/*
要判断指数和底数, 先按照指数分类
exponent > 0
底数没有限制
exponent ==0
底数为0
底数不为0
exponent < 0
底数为0
底数不为0(倒数)
进一步地,可以分成两种情况
exponent == 0
exponent != 0
*/
public class Solution {
public double Power(double base, int exponent) {
double res = 0;
if(exponent == 0){
if(base == 0)
throw new RuntimeException();
else
res = 1;
}
else if(exponent > 0)
res = pow(base, exponent);
else{
if(base == 0)
throw new RuntimeException();
else{
res = 1/pow(base, -exponent);
}
}
return res;
}
public double pow(double base, int exponent){
double res = 1;
for(int i=exponent; i>0; i--)
res *= base;
return res;
}
}
第二次 递归求指数过程,可以减少连乘的次数
public class Solution {
public double Power(double base, int exponent) {
double res = 0;
if(exponent == 0){
if(base == 0)
throw new RuntimeException();
else
res = 1;
}
else if(exponent > 0)
res = pow(base, exponent);
else{
if(base == 0)
throw new RuntimeException();
else{
res = 1/pow(base, -exponent);
}
}
return res;
}
//递归函数逻辑:求base的exponent幂
public double pow(double base, int exponent){
//base case
if(exponent==0)
return 1;
//
double res = base;
double temp = pow(base, exponent/2);
if((exponent & 1) == 1)
res = base * temp * temp;
else
res = temp * temp;
return res;
}
}
public class Solution {
public double Power(double base, int exponent) {
/*
底数分成两种:(1)非零, (2)0
指数分成三种:(1)正数, (2)负数, (3)0
具体地,
1.底数非零时,分为两种情况
1.1指数大于等于0:直接求次方
1.2指数小于0:base要取倒数; 还要注意把exponent变为正数,表示base自乘exponent次
2.底数是零时,指数需要分情况讨论
2.1指数是正数:返回0
2.2指数是0:返回0(这个自己定义)
2.3指数是负数:不允许,因为0无法作为分母
*/
double result=1;
//1.
if(base != 0 ){
//1.2
if(exponent<0){
exponent = -1*exponent;
base = 1/base;
}
//1.1
for(int i=0; i<exponent; i++)
result *= base;
return result;
}
//2.1, 2.2
if(exponent>= 0)
return 0;
//2.3
return 0;
}
}
更高效的代码
思路
- 不再采用逐个相乘的方式,也就是指数每次增加1
- 令指数也成指数变化,比如指数依次为:1,2,4,8,…
- base^exponent的结果可以根据exponent的奇偶分成两种情况
- 情况一: exponent为偶数, 那么base^exponent = base^(exponent/2)*base^(exponent/2)
- 情况二: exponent为奇数, 那么base^exponent = base^((exponent-1)/2)*base^((exponent-1)/2)*base
- 代码中(exponent-1)/2直接写成exponent/2即可
- 指数不断除以2,最终会变成…8,4,2,1,0 注意找好递归终止条件
- 最后要额外注意, 在递归函数中,base是double类型, 因为指数为负数时,底数要取倒数,所以得用double类型, 这是个细节
public class Solution {
public double Power(double base, int exponent) {
/*
底数分成两种:(1)非零, (2)0
指数分成三种:(1)正数, (2)负数, (3)0
具体地,
1.底数非零时,分为两种情况
1.1指数大于等于0:直接求次方
1.2指数小于0:base要取倒数; 还要注意把exponent变为正数,作为乘几次的标志
2.底数是零时,指数需要分情况讨论
2.1指数是正数:返回0
2.2指数是0:返回0(这个自己定义)
2.3指数是负数:不允许,因为0无法作为分母
*/
double result=1;
//1.
if(base != 0 ){
//1.2
if(exponent<0){
exponent = -1*exponent;
base = 1/base;
}
//1.1
return PowerCore(base, exponent);
}
//2.1, 2.2
if(exponent>= 0)
return 0;
//2.3
return 0;
}
private double PowerCore(double base, int exponent){ // 递归函数中, base是double类型, 为了处理负指数,需要把之前的base取到数
if(exponent==0)
return 1;
double result = 1;
double yinShu = PowerCore(base, exponent/2);
if((exponent&1)==1)//如果指数是奇数,结果要多乘一个base
result *= base;
result = yinShu*yinShu*result;
return result;
}
}