面试题16. 数值的整数次方

题目: 实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
示例:
示例 1:

输入: 2.00000, 10
输出: 1024.00000

示例 2:

输入: 2.10000, 3
输出: 9.26100

示例 3:

输入: 2.00000, -2
输出: 0.25000
解释: 2^-2 = 1/2^2 = 1/4 = 0.25

说明:

解题思路:

    • 一、暴力法,时间超过限制
    • 二、快速幂
      • (一)常规解法
      • (二)借助位运算

一、暴力法,时间超过限制

这个是我们最容易想到的办法,一次for循环就可以解决,但是时间复杂度O(N)超出限制,不能通过题解,代码如下:

double myPow1(double x, int n) 
{
	if(x==1 || n==0)
		return 1;
	double sum=1;
	int flag=0;
	if(n<0)
	{
		flag=1;
		n*=-1;
	}
	for(int i=0;i<n;i++)
	{
		sum*=x;
	}
	if(flag)
	{
		return 1/sum;
	}
	return sum;
}

0(N)时间复杂度超出限制,那么正确题解的时间复杂度一定是O(logN),我们想办法往这个时间复杂度上靠近,很容易想到二分,分为2部分进行相乘,那样可以将乘的次数减少一半,这就是基于二分的快速幂。

二、快速幂

假如我们需要求解36,那么可以分解为:(32)3→(93)→(92)*9=81*9=729。每次底数以x2进行递增,指数以num/2变化,分为奇数和偶数情况。那么我们可以总结出一个规律:
面试题16. 数值的整数次方_第1张图片
因为奇数情况,用除法和偶数的结果一样,如3/2=1,2/2=1,所以不用n-1。我们可以看到这是一种二分的形式,即将n进行二分,求x4=x2*x2,而不是x4=x*x*x*x。如果是奇数的话就会多出来一项,所以我们用res保存结果时,当n为奇数,就将x乘入res,再进行x2,最后n==1时,x已经不断x2累乘计算完毕,此时n为奇数,将x乘入res,得出结果,n/2=0循环结束。
我们可以根据上述的思路进行一个示例演示,假如现在计算55,我们进行过程演示:

n幂值(初始值5) 奇偶(奇数) res(1) x(5)
n=5 奇数 1*5=5 5^5=25
n=5/2=2 偶数 5 25*25=625
n=2/2=1 奇数 5*625=3125 625*625=390625
n=1/2=0 退出循环,返回res=3125

计算机验证结果正确,那么我们可以总结思路,需要考虑到负数等情况:

  • 如果x=1,或者n=0,那么直接返回1即可。
  • 如果n为负数,需要求x的倒数平方,那么x=1/x;n=-n;如果n用int保存,int有符号整形变量的范围是[-2147483648 2147483647];当-2147483648取绝对值时大于2147483647就会报错出现溢出,所以先对负数进行强制类型转换,用long存储即可。
  • 进行while循环,直到n==0时退出。
  • 循环体内,进行奇数偶数判断,奇数:res*=x;
  • 进行x*=x累加,n/=2不断二分缩小。
  • 循环结束返回res即可

(一)常规解法

我们可以根据思路写出代码:

//2.快速幂运算,不引入位运算,3^3=3^2*3^1,所以我们可以不断计算x的平方,分为奇数,偶数两种情况
double myPow2(double x, int n) 
{
	if(x==1 || n==0)
		return 1;
	long num=n;//int会溢出,所以用long存储因为有符号整形变量的范围是-2147483648    2147483647;当-2147483648取绝对值时大于2147483647就会报错,所以先对负数进行强制类型转换。
	double res=1;
	if(n<0)
	{
		num=-num;
		x=1/x;
	}
	while(num!=0)
	{
		if(num%2==1)//奇数
		{
			res*=x;
		}
		x*=x;
		num/=2;
	}
	return res;
}

(二)借助位运算

我们可以借助位运算来优化代码,降低时间复杂度,我们用到了

num%2==1;//判断奇数偶数
num/=2;//二分

可以用位运算来表示这两句,所有奇数的二进制,0号位置一定是1,所以可以

(num & 1)==1;//加括号表明执行优先级

来判断奇数偶数,如果0号位置为1,返回真,进行res*=x;否则不进入if判断体中。
左移1位表示增大一倍数值,右移1位表示缩小一倍数值,所以,我们可以用>>表示除2运算符:

num>>=1;

那么优化后的代码为:

double myPow(double x, int n) 
{
	if(x==1 || n==0)
		return 1;
	long num=n;
	double res=1;
	if(n<0)
	{
		num=-num;
		x=1/x;
	}
	while(num!=0)
	{
		if((num&1)==1)//奇数,与运算可以判断奇数,奇数它一定在0位上有1
		{
			res*=x;
		}
		x*=x;
		num=num>>1;//右移就是/2的含义
	}
	return res;
}

加油哦!。

你可能感兴趣的:(剑指offer习题练习)