题目:实现函数double Power(double base, int exponent),求base的exponent次方。不需要考虑溢出。

分析:这是一道看起来很简单的问题。可能有不少的人在看到题目后30秒写出如下的代码:

double Power(double base, int exponent)
{double result = 1.0;for(int i = 1; i <= exponent; ++i)result *= base;return result;
}

上述代码至少有一个问题:由于输入的exponent是个int型的数值,因此可能为正数,也可能是负数。上述代码只考虑了exponent为正数的情况。

接下来,我们把代码改成:

bool g_InvalidInput = false;double Power(double base, int exponent)
{g_InvalidInput = false;if(IsZero(base) && exponent < 0){g_InvalidInput = true;return 0.0;}unsigned int unsignedExponent = static_cast<unsigned int>(exponent);if(exponent < 0)unsignedExponent = static_cast<unsigned int>(-exponent);double result = PowerWithUnsignedExponent(base, unsignedExponent);if(exponent < 0)result = 1.0 / result;return result;
}double PowerWithUnsignedExponent(double base, unsigned int exponent)
{double result = 1.0;for(int i = 1; i <= exponent; ++i)result *= base;return result;
}

上述代码较之前的代码有了明显的改进,主要体现在:首先它考虑了exponent为负数的情况。其次它还特殊处理了当底数base为0而指数exponent为负数的情况。如果没有特殊处理,就有可能出现除数为0的情况。这里是用一个全局变量来表示无效输入。关于不同方法来表示输入无效的讨论,详见本系列第17题。

最后需要指出的是:由于0^0次方在数学上是没有意义的,因此无论是输入0还是1都是可以接受的,但需要在文档中说明清楚。

这次的代码在逻辑上看起来已经是很严密了,那是不是意味了就没有进一步改进的空间了呢?接下来我们来讨论一下它的性能:

如果我们输入的指数exponent为32,按照前面的算法,我们在函数PowerWithUnsignedExponent中的循环中至少需要做乘法31次。但我们可以换一种思路考虑:我们要求出一个数字的32次方,如果我们已经知道了它的16次方,那么只要在16次方的基础上再平方一次就可以了。而16次方是8次方的平方。这样以此类推,我们求32次方只需要做5次乘法:求平方,在平方的基础上求4次方,在4次方的基础上平方求8次方,在8次方的基础上求16次方,最后在16次方的基础上求32次方。

32刚好是2的整数次方。如果不巧输入的指数exponent不是2的整数次方,我们又该怎么办呢?我们换个数字6来分析,6就不是2的整数次方。但我们注意到6是等于2+4,因此我们可以把一个数的6次方表示为该数的平方乘以它的4次方。于是,求一个数的6次方需要3次乘法:第一次求平方,第二次在平方的基础上求4次方,最后一次把平方的结果和4次方的结果相乘。

现在把上面的思路总结一下:把指数分解了一个或若干个2的整数次方。我们可以用连续平方的方法得到以2的整数次方为指数的值,接下来再把每个前面得到的值相乘就得到了最后的结果。

到目前为止,我们还剩下一个问题:如何将指数分解为一个或若干个2的整数次方。我们把指数表示为二进制数再来分析。比如6的二进制表示为110,在它的第2位和第3位为1,因此6=2^(2-1)+2^(3-1) 。也就是说只要它的第n位为1,我们就加上2的n-1次方。

最后,我们根据上面的思路,重写函数PowerWithUnsignedExponent:

double PowerWithUnsignedExponent(double base, unsigned int exponent)
{std::bitset<32> bits(exponent);if(bits.none())return 1.0;int numberOf1 = bits.count();double multiplication[32];for(int i = 0; i < 32; ++i){multiplication[i] = 1.0;}// if the i-th bit in exponent is 1,// the i-th number in array multiplication is base ^ (2 ^ n)int count = 0;double power = 1.0;for(int i = 0; i < 32 && count < numberOf1; ++i){if(i == 0)power = base;elsepower = power * power;if(bits.at(i)){multiplication[i] = power;++count;}}power = 1.0;for(int i = 0; i < 32; ++i){if(bits.at(i))power *= multiplication[i];}return power;
}

在上述代码中,我们用C++的标准函数库中bitset把整数表示为它的二进制,增大代码的可读性。如果exponent的第i位为1,那么在数组multiplication的第i个数字中保存以base为底数,以2的i次方为指数的值。最后,我们再把所以位为1在数组中的对应的值相乘得到最后的结果。

上面的代码需要我们根据base的二进制表达的每一位来确定是不是需要做乘法。对二进制的操作很多人都不是很熟悉,因此编码可能觉得有些难度。我们可以换一种思路考虑:我们要求出一个数字的32次方,如果我们已经知道了它的16次方,那么只要在16次方的基础上再平方一次就可以了。而16次方是8次方的平方。这样以此类推,我们求32次方只需要做5次乘法:先求平方,在平方的基础上求4次方,在4次方的基础上平方求8次方,在8次方的基础上求16次方,最后在16次方的基础上求32次方。

也就是说,我们可以用如下公式求a的n次方:

这个公式很容易就能用递归来实现。新的PowerWithUnsignedExponent代码如下:

double PowerWithUnsignedExponent(double base, unsigned int exponent)
{if(exponent == 0)return 1;if(exponent == 1)return base;double result = PowerWithUnsignedExponent(base, exponent >> 1);result *= result;if(exponent & 0x1 == 1)result *= base;return result;
}

本文已经收录到《剑指Offer——名企面试官精讲典型编程题》一书中,有改动,书中的分析讲解更加详细。欢迎关注。

博主何海涛对本博客文章享有版权。网络转载请注明出处http://zhedahht.blog.163.com/。整理出版物请和作者联系。

程序员面试题精选100题(44)-数值的整数次方[算法]相关推荐

  1. 程序员面试题精选100题(49)-复杂链表的复制[算法]

    题目:有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL.其结点的C++定义如下: struct ComplexNode ...

  2. 程序员面试题精选100题(23)-跳台阶问题[算法]

    题目:一个台阶总共有n级,如果一次可以跳1级,也可以跳2级.求总共有多少总跳法,并分析算法的时间复杂度. 分析:这道题最近经常出现,包括MicroStrategy等比较重视算法的公司都曾先后选用过个这 ...

  3. 程序员面试题精选100题(21)-左旋转字符串[算法]

    题目:定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部.如把字符串abcdef左旋转2位得到字符串cdefab.请实现字符串左旋转的函数.要求时间对长度为n的字符串操作的复杂度为O( ...

  4. 程序员面试题精选100题:41-50解题报告

    程序员面试题精选100题(41)-把数组排成最小的数[算法]   题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个.例如输入数组{32,  321},则输出这两个能 ...

  5. 程序员面试题精选100题

    程序员面试题精选100题(01)-把二元查找树转变成排序的双向链表 题目:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表.要求不能创建任何新的结点,只调整指针的指向. 比如将二元查找树   ...

  6. [程序员面试题精选100题]13.第一个只出现一次的字符

    [题目] 在一个字符串中找到第一个只出现一次的字符.如输入abaccdeff,则输出b. [分析] [代码] /********************************* * 日期:2013- ...

  7. 程序员面试题精选100题(51)-顺时针打印矩阵

    // 程序员面试题精选100题(51)-顺时针打印矩阵.cpp : 定义控制台应用程序的入口点. //#include "stdafx.h" #include <iostre ...

  8. 程序员面试题精选100题:求从1到n的正数中1出现的次数

    // 程序员面试题精选100题(25):求从1到n的正数中1出现的次数 // 如 f(253) = (2!=0) * 100 + 2 * f(99) + (5!=0) * 10 + 5 * f(9) ...

  9. 程序员面试题精选100题:11-40解题报告

    程序员面试题精选100题(11)-求二元查找树的镜像[数据结构]   题目:输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点.用递归和循环两种方法完成 ...

最新文章

  1. 12个现实世界中的机器学习真相
  2. NR 5G 零基础看5G
  3. 互联网音乐还有什么机会
  4. 网管员心声:Windows服务有“备”无患
  5. 25岁,在一个需要工作的周末……
  6. Pytorch实战1:线性回归(Linear Regresion)
  7. java数据跑不出来,6000条数据,java下跑了20多分钟了还没跑完,求教怎么改进
  8. 李彦宏千字愿景内部信:10次提到“用户”
  9. sql server使用convert来取得datetime日期数据
  10. 一种人是成功为了赚钱,一种人是赚钱为了成功
  11. java 维文生成图片_维文、哈萨克文、柯尔克孜文检测 (java实现把UTF-8转为unicode)...
  12. 机器学习中分类和聚类的区别
  13. matlab计算abc三相短路电流_供配电系统设计需要用到的计算公式(结合手册简要总结)...
  14. c语言打开文件并输出,文件的读取,c语言打开文件并输出
  15. 视频网站套上CDN是什么效果?
  16. cannot access memory
  17. Clojure学习笔记(一)——介绍、安装和语法
  18. cip数据核字号查询(图书cip数据核字号查询)
  19. 520情人节送女朋友的3D相册礼物~html+css+js实现抖音炫酷樱花3D相册(含音乐)
  20. 优秀工程师应该具备哪些素质_想成为一名合格的技术工程师需要具备哪些能力?...

热门文章

  1. jQuery快速入门
  2. 像疯狗一般,你就具备了向上的资格
  3. GMIS 2017大会邓力演讲:无监督学习的前沿与SPDG方法的优良性
  4. Java 8 - CompletableFuture组合式异步编程
  5. 实战SSM_O2O商铺_21【商铺列表】Dao层开发
  6. Spring-基于Java类的配置
  7. Spring-基于注解的配置[03Bean作用范围和生命周期方法]
  8. AS插件-android-selector-chapek
  9. 基础JavaScript_Day03
  10. Redis之压缩链表ziplist