跟learnjiawa一起每天一道算法编程题,既可以增强对常用API的熟悉能力,也能增强自己的编程能力和解决问题的能力。算法和数据结构,是基础中的基础,更是笔试的重中之重。
- 不积硅步,无以至千里;
- 不积小流,无以成江海。
题目描述
Java版剑指offer编程题第12题–数值的整数次方: 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0
我的想法
- 直接累乘,如果exponent指数为n,你就要累乘n-1次,时间复杂度记为O(n),这里n趋于无穷大,所以省去“-1”。
- 采用二分思想,对于最朴素的想法,77=49,497=343,… 一步一步算,共进行了9次乘法。这样算无疑太慢了,尤其对计算机的CPU而言,每次运算只乘上一个个位数,无疑太屈才了。这时我们想到,也许可以拆分问题,即递归快速幂。
先算7的5次方,即77777,再算它的平方,共进行了5次乘法。但这并不是最优解,因为对于“7的5次方”,我们仍然可以拆分问题。
先算77得49,则7的5次方为4949*7,再算它的平方,共进行了4次乘法。 - 递归虽然简洁,但会产生额外的空间开销。我们可以把递归改写为循环,来避免对栈空间的大量占用,也就是非递归快速幂。换一个角度来引入非递归的快速幂。
还是7的10次方,但这次,我们把10写成二进制的形式,也就是1010 。现在我们要计算7^(1010) ,可以怎么做?
我们很自然地想到可以把它拆分为7 ^(1000)*7 ^ (0010) . 实际上,对于任意的整数,我们都可以把它拆成若干个 7 ^ (100…) 的形式相乘。而这些7 ^ (100…),恰好就是 7 ^ 1,7 ^ 2,7 ^ 4。
解题方法1
/**
* 直接累乘,如果exponent指数为n,你就要累乘n-1次
* 时间复杂度记为O(n),这里n趋于无穷大,所以省去“-1”
* */
public static double Power(double base, int exponent) {
double result=1;
int abs_exponent;
/**
* 分别考虑指数为0或底数为0的特殊情况
* */
if(exponent == 0){
return 1;
}
if(base == 0){
return 0;
}
//指数大于0
if(exponent > 0){
abs_exponent = exponent;
for(int i = 0; i < abs_exponent; i++){
result=result*base;
}
return result;
}else{
//指数小于0,先将指数取反,然后返回计算结果的倒数
abs_exponent = -exponent;
for(int i = 0; i < abs_exponent; i++){
result=result*base;
}
return 1/result;
}
}
解题方法2
/**
* 解法2:递归快速幂,非递归快速幂,时间复杂度:O(logn)
* 1. 最朴素的想法,7*7=49,49*7=343,... 一步一步算,共进行了9次乘法。
*这样算无疑太慢了,尤其对计算机的CPU而言,每次运算只乘上一个个位数,无疑太屈才了。这时我们想到,也许可以拆分问题。
* 2. 先算7的5次方,即7*7*7*7*7,再算它的平方,共进行了5次乘法。
*但这并不是最优解,因为对于“7的5次方”,我们仍然可以拆分问题。
* 3. 先算7*7得49,则7的5次方为49*49*7,再算它的平方,共进行了4次乘法。*/
public static double Power2(double base, int exponent) {
double result=1;
int abs_exponent;
/**
* 分别考虑指数为0或底数为0的特殊情况
* 递归出口
* */
if(exponent == 0){
return 1;
}
if(base == 0){
return 0;
}
//指数大于0
if(exponent > 0){
abs_exponent = exponent;
//指数为奇数
if(abs_exponent % 2 ==1){
return Power2(base,abs_exponent-1)*base;
}else{
//指数为偶数
double temp = Power2(base, abs_exponent/2);
return temp*temp;
}
}else{
//指数小于0,先将指数取反,返回计算结果的倒数
abs_exponent = -exponent;
if(abs_exponent % 2 ==1){
//指数为奇数
return 1/(Power2(base,abs_exponent-1)*base);
}else{
//指数为偶数
double temp = Power2(base, abs_exponent/2);
return 1/(temp*temp);
}
}
}
解题方法3
//非递归快速幂,时间复杂度:O(logn)
public static double Power3(double base, int exponent) {
double result=1;
int abs_exponent;
/**
* 分别考虑指数为0或底数为0的特殊情况
* 递归出口
* */
if(exponent == 0){
return 1;
}
if(base == 0){
return 0;
}
//存储中间计算结果
double curr = base;
//考虑指数的正负性
if(exponent < 0){
abs_exponent = -exponent;
}else{
abs_exponent = exponent;
}
// 注意递归和非递归的快速幂的关系,方便理解
while(abs_exponent > 0) {
//这里的位运算相当于递归快速幂中的abs_exponent % 2 ==1
if ((abs_exponent & 1) == 1) {
result *= curr;
}
curr *= curr;
//右移,相当于 /2
abs_exponent = abs_exponent >> 1;
}
if(exponent > 0){
return result;
}else{
return 1/result;
}
}
代码测试
package com.learnjiawa.jzoffer;
/**
* @author learnjiawa
* 2019-12-11-10:29
*/
public class Solution12 {
public static void main(String[] args) {
double base = 7.0;
int exponent = 10;
System.out.println("累乘法计算数字"+ base + "的" + exponent + "次幂是"+Power(base,exponent));
System.out.println("递归快速幂计算数字"+ base + "的" + exponent + "次幂是"+Power(base,exponent));
System.out.println("非递归快速幂计算数字"+ base + "的" + exponent + "次幂是"+Power(base,exponent));
}
/**
* 直接累乘,如果exponent指数为n,你就要累乘n-1次
* 时间复杂度记为O(n),这里n趋于无穷大,所以省去“-1”
* */
public static double Power(double base, int exponent) {
double result=1;
int abs_exponent;
/**
* 分别考虑指数为0或底数为0的特殊情况
* */
if(exponent == 0){
return 1;
}
if(base == 0){
return 0;
}
//指数大于0
if(exponent > 0){
abs_exponent = exponent;
for(int i = 0; i < abs_exponent; i++){
result=result*base;
}
return result;
}else{
//指数小于0,先将指数取反,然后返回计算结果的倒数
abs_exponent = -exponent;
for(int i = 0; i < abs_exponent; i++){
result=result*base;
}
return 1/result;
}
}
/**
* 解法2:递归快速幂
* 1. 最朴素的想法,7*7=49,49*7=343,... 一步一步算,共进行了9次乘法。
*这样算无疑太慢了,尤其对计算机的CPU而言,每次运算只乘上一个个位数,无疑太屈才了。这时我们想到,也许可以拆分问题。
* 2. 先算7的5次方,即7*7*7*7*7,再算它的平方,共进行了5次乘法。
*但这并不是最优解,因为对于“7的5次方”,我们仍然可以拆分问题。
* 3. 先算7*7得49,则7的5次方为49*49*7,再算它的平方,共进行了4次乘法。*/
public static double Power2(double base, int exponent) {
double result=1;
int abs_exponent;
/**
* 分别考虑指数为0或底数为0的特殊情况
* 递归出口
* */
if(exponent == 0){
return 1;
}
if(base == 0){
return 0;
}
//指数大于0
if(exponent > 0){
abs_exponent = exponent;
//指数为奇数
if(abs_exponent % 2 ==1){
return Power2(base,abs_exponent-1)*base;
}else{
//指数为偶数
double temp = Power2(base, abs_exponent/2);
return temp*temp;
}
}else{
//指数小于0,先将指数取反,返回计算结果的倒数
abs_exponent = -exponent;
if(abs_exponent % 2 ==1){
//指数为奇数
return 1/(Power2(base,abs_exponent-1)*base);
}else{
//指数为偶数
double temp = Power2(base, abs_exponent/2);
return 1/(temp*temp);
}
}
}
public static double Power3(double base, int exponent) {
double result=1;
int abs_exponent;
/**
* 分别考虑指数为0或底数为0的特殊情况
* 递归出口
* */
if(exponent == 0){
return 1;
}
if(base == 0){
return 0;
}
//存储中间计算结果
double curr = base;
//考虑指数的正负性
if(exponent < 0){
abs_exponent = -exponent;
}else{
abs_exponent = exponent;
}
// 注意递归和非递归的快速幂的关系,方便理解
while(abs_exponent > 0) {
//这里的位运算相当于递归快速幂中的abs_exponent % 2 ==1
if ((abs_exponent & 1) == 1) {
result *= curr;
}
curr *= curr;
//右移,相当于 /2
abs_exponent = abs_exponent >> 1;
}
if(exponent > 0){
return result;
}else{
return 1/result;
}
}
}
总结
题目的考察知识点是快速幂算法,可以选择使用递归或者位运算,减少运算次数,减小时间复杂度,明天见哦!
参考文献
[1]程杰. 大话数据结构. 北京:清华大学出版社, 2011.
更多
对我的文章感兴趣,点个关注是对我最大的支持,持续更新中…
关注微信公众号LearnJava: