题目介绍
利空i231题:https://leetcode-cn.com/problems/power-of-two/
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
分析
本题是一个数学问题。在计算机系统中,2的整次幂往往有非常重要的意义,往往是众多数据类型能够表示的数值边界,是真正的“整数”。2的整次幂,有很多重要的性质:
- 能被2连续整除
- 写成2进制的形式,就是一个1后面跟着n个0(n≥0)
我们可以利用这些特性,构造出不同的解法。
方法一:除2判断余数
最简单的想法,就是借鉴十进制转换二进制的方法,不断地除以2,判断当前余数是否为0,只要中间出现了1,那就不是2的整次幂。这里需要注意的是,即使是2的整次幂,最后也应该有一次余数为1。
那如何判断当前余数1是“中间产生”还是“最终产生”呢?只需要看当前的被除数是否为1就可以了:如果是最后一次除法,被除数应该就是1。
代码如下:
public class PowerOfTwo {
// 方法一:除2判断余数
public boolean isPowerOfTwo(int n){
if (n <= 0) return false;
// 不停地除以2
while (n % 2 == 0)
n /= 2;
return n == 1;
}
}
复杂度分析
- 时间复杂度:O(logn)。循环的次数,就是二进制展开的位数,也就是以2为底n的对数。
- 空间复杂度:O(1)。
方法二:位运算(与自身减1做位与)
对于这样一个数学问题,O(logn)的时间复杂度显然不能让我们满意。我们可以利用之前分析的2的幂第二个特性来进行优化,也就是说:写成2进制形式后,就是一个1后面跟着k个0(k≥0)。
这种形式有一个非常明显的性质,就是减1之后,就会变成k个1。所以我们可以发现,对于2的幂,这个数自身和它减1之后的数,进行位与运算,得到的结果应该是0;或者进行异或运算,得到的结果应该是k+1个1。
当然很明显,位与运算的结果更好判断。写成逻辑表达式就是:
n & (n - 1) == 0
代码如下:
// 方法二:位运算(与自身减1做位与)
public boolean isPowerOfTwo(int n){
if (n <= 0) return false;
return (n & n - 1) == 0;
}
复杂度分析
- 时间复杂度:O(1)。只需要做一次位运算。
- 空间复杂度:O(1)。
方法三:位运算(与相反数做位与)
由于2的幂的二进制表达中,只有一个1,所以另外一个思路是,我们可以试图获取二进制数中最右端的1。我们现在要保留最后一位1,其它位全部变0,如果等于自身,那就是2的幂。
要想其它位都变为0,容易想到,如果取反码,然后跟自身做位与,自然就全是0了。如果再加1,就可以保留最后一位1了。
我们知道,在计算机底层,负数的补码表示,就是反码加1。所以我们要做的,就是让n和-n做位与运算。得到的,就只有最右面一位1,其它位都为0。
如果当前数n为2的幂,那么最右面的一位1,其实就是最高位的1;保留这一位,其实跟原数是完全相等的。
写成表达式就是:
n & (-n) == n
代码如下:
// 方法三:位运算(与相反数做位与)
public boolean isPowerOfTwo(int n){
if (n <= 0) return false;
return (n & -n) == n;
}
复杂度分析
- 时间复杂度:O(1)。
- 空间复杂度:O(1)。