Leetcode刷题:剑指offer【面试题16 数值的整数次方】

文章目录

  • 思路 1:循环相乘
  • 思路 2:二分法(递归实现)
  • 思路 3:二分法(非递归实现)

【面试题16 数值的整数次方】

难度: 中等

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

Leetcode题目对应位置: 面试题16:数值的整数次方

实现特定库函数(处理数值和字符串的函数)的功能是一类常见的面试题。

测试考虑:

  • 指数 n 可能为正或负,当它是负数时,就要先对 n 求绝对值,算出底数 xn 次方后再去倒数
  • 若底数 x 为 0,且指数是负数时,若不进行特殊处理,会出现对 0 求倒数从而报错
  • 0 的 0 次方在数学上没有意义,答案可以是 0 或者 1,这一点需要和面试官说清楚,证明我们是考虑到这个边界值的

思路 1:循环相乘

最直接的思路应该就是 base 循环相乘,这样做时间复杂度为 O(n),效率不高。

python 代码:

class Solution:
    def myPow(self, x: float, n: int) -> float:
        if x == 0 and n < 0: return -1
        absN = n
        if n < 0: absN = -n
        res = self.power(x, absN)
        if n < 0: res = 1 / res
        return res
    
    def power(self, x, n):
        res = 1
        for i in range(n):
            res *= x
        return res

C++ 代码:

#include 
#include 

bool g_InvalidInput = false;
bool equal(double num1, double num2);
double PowerWithUnsignedExponent(double base, unsigned int exponent);

double Power(double base, int exponent)
{
	g_InvalidInput = false;
	// 处理底数为0,指数为负的情况
	if (equal(base, 0.0) && exponent < 0)
	{
		g_InvalidInput = true;
		return 0.0;
	}

	// 处理指数为负的情况 -> 取绝对值
	unsigned int absExponent = (unsigned int)(exponent);
	if (exponent < 0) {
		absExponent = (unsigned int)(-exponent);
	}

	// 计算次方
	double result = PowerWithUnsignedExponent(base, absExponent);
	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;
}

bool equal(double num1, double num2)
{
	if ((num1 - num2 > -0.00000001) && (num1 - num2 < 0.00000001))
		return true;
	else
		return false;
}

在 C++ 代码中,出错处理办法是设置全局变量 g_InvalidInput 来标识是否出错,若出错则返回值是 0 且全局变量 g_InvalidInput 为 true,用以区分出错时返回的 0 和正常计算返回的 0。这样做的好处是函数返回值可以作为参数传递给其他函数。但是这种出错处理办法的隐患在于:调用者可能会忘记检查全局变量。

思路 2:二分法(递归实现)

第一种思路循环做乘,若需要求解 n 次方,就得做 n-1 次乘法,效率低下。

二分法求解思路: 假设要求 x 的 32 次方,那么只要知道了 x 的 16 次方,只要在 16 次方的基础上平方一次就可以了;而 16 次方又是 8 次方的平方…以此类推,实际上求 32 次方只需要做 5 次乘法:x = x*x,通过循环更新 x,就能得到 x^2x^4x^8x^16x^32

更一般地,对于 x 的 n 次方:
x n = { x n / 2 × x n / 2                         n 为 偶 数 x ( n − 1 ) / 2 × x ( n − 1 ) / 2 × x       n 为 奇 数 x^n=\begin{cases} x^{n/2} \times x^{n/2}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ n为偶数 \\ x^{(n-1)/2} \times x^{(n-1)/2} \times x\ \ \ \ \ n为奇数 \\ \end{cases} xn={xn/2×xn/2                       nx(n1)/2×x(n1)/2×x     n
该公式就是典型地通过递归来实现,相关题目:Leetcode刷题:剑指offer【面试题10-1 斐波那契数列】

时间复杂度: O ( l o g n ) O(logn) O(logn)
空间复杂度: O ( l o g n ) O(logn) O(logn)

代码逻辑:

  • 递归:每次将 n 降至 n // 2(向下取整),直至 n 降为 1 时返回底数 x 本身(res)。在递归函数中,首先计算 res * res,若 n 为奇数,则 res * x
  • 用右移运算代替 n // 2,用位与运算代替求余(%)来判断 n 是奇数还是偶数,因为位运算的效率比乘除法更高
  • 特殊值处理:1)若 x 等于 0 且 n 小于 0,执行出错返回 -1;2)若 x 等于 0,直接返回 0;3)若 n 小于 0,则取 n 的绝对值并对 x 取倒数

Python 代码:

class Solution:
    def myPow(self, x: float, n: int) -> float:
        if x == 0 and n < 0: return -1
        if x == 0: return 0
        if n < 0: x, n = 1 / x, -n
        return self.powerWithUnsignedExponent(x, n)
    
    def powerWithUnsignedExponent(self, x, n):
        if n == 0: return 1
        if n == 1: return x
        res = self.powerWithUnsignedExponent(x, n >> 1)
        res *= res
        if n & 1 == 1:   # n为奇数
            res *= x
        return res

C ++ 代码只需要修改思路 1 中的 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;
}

思路 3:二分法(非递归实现)

Python 代码实现:

class Solution:
    def myPow(self, x: float, n: int) -> float:
        if x == 0 and n < 0: return -1
        if x == 0: return 0
        if n < 0: x, n = 1 / x, -n
        res = 1
        while n:
            if n & 1: res *= x
            x *= x
            n >>= 1
        return res

代码来源:mian-shi-ti-16-shu-zhi-de-zheng-shu-ci-fang-kuai-s

发现一个很有意思的测试用例:

1.00000
-2147483648

用 C++ 写的话会报溢出,所以用强制类型转换来打个辅助。溢出的原因是有符号 int 类型变量在 C 里的范围是[-2147483648, 2147483647],如果对 -2147483648 直接取绝对值就会导致溢出,所以先用 unsigned int 对 n 进行强制类型转换就 ok 啦。

Line 12: Char 17: runtime error: negation of -2147483648 cannot be represented in type 'int'; cast to an unsigned type to negate this value to itself (solution.cpp)

C++ 代码实现:

class Solution {
public:
    bool g_InvalidInput = false;
    double myPow(double x, int n) {
        if (x == 0 && n < 0){
            g_InvalidInput = true;
            return 0.0;
        }

        unsigned int m = (unsigned int) n;  // 解决溢出
        if (n < 0){
            x = 1 / x;
            m = -m;
        }

        double res = 1;
        while(m){
            if (m & 1 == 1)
                res *= x;
            x *= x;
            m >>= 1;
        }

        return res;
    }
};

在这里插入图片描述


参考资料:
[1] 剑指 offer 第二版
[2] LeetCode 面试题 16 参考题解

你可能感兴趣的:(今天刷题了吗)