题目描述(简单难度)
判断一个数是不是 2
的幂次。
思路分析
题目比较简单,有很多解法,看了其他人的解法,各种秀操作,哈哈。解法一和解法二是我开始想到的,后边的解法是其他人的也总结到这里。
解法一
介绍一种暴力的方法,判断 1
和当前数是否相等,再判断 2
和当前数是否相等,再判断 4
和当前数是否相等…直到所枚举的数超过了当前数,那么就返回 false
。至于枚举的数 1 2 4 8 16...
,可以通过移位得到。
public boolean isPowerOfTwo(int n) {
int power = 1;
while (power <= n) {
if (power == n) {
return true;
}
power = power << 1;
// if (power == Integer.MIN_VALUE)
if (power == -2147483648) {
break;
}
}
return false;
}
当然有一点需要注意,需要了解一些补码的知识,参考 趣谈补码。
对于 int
类型,最大的 2
的幂次是 2 的 30 次方,即1073741824
,二进制形式是 0100000...00
。
最大的负数是 -2147483648
,二进制形式是 1000000...00
。
可以发现前者左移一位刚好变成了后者,这也是代码中判断是否是 -2147483648
的原因,不然的话会造成死循环的。
解法二
我们把数字放眼到二进制形式,列举一下 2
的幂次。
1 1
2 10
4 100
8 1000
16 10000
...
可以发现发现都是 100...
的形式,如果做过 201 题,对 Integer.highestOneBit
方法可能还记得。可以实现保留最高位的 1
,然后将其它位全部置为 0
。即,把 0 0 0 1 X X X X
变成 0 0 0 1 0 0 0 0
。
如果我们对给定的数 n
调用这个方法,如果 n
是 2
的幂次,那么它还是它本身。如果是其他数,由于其它位被置 0
了,所以它一定不等于它本身了。
public boolean isPowerOfTwo(int n) {
if (n == 0 || n == -2147483648) {
return false;
}
return Integer.highestOneBit(n) == n;
}
0
和 -2147483648
不符合上边的规则,单独考虑,我们可以更干脆一些,小于等于 0
的数直接不考虑。
public boolean isPowerOfTwo(int n) {
if (n <= 0) {
return false;
}
return Integer.highestOneBit(n) == n;
}
201 题 的解法三对 highestOneBit
的源码进行了分析,下边把之前的解析贴过来。
我们调用了库函数 Integer.highestOneBit
,我们去看一下它的实现。
/**
* Returns an {@code int} value with at most a single one-bit, in the
* position of the highest-order ("leftmost") one-bit in the specified
* {@code int} value. Returns zero if the specified value has no
* one-bits in its two's complement binary representation, that is, if it
* is equal to zero.
*
* @param i the value whose highest one bit is to be computed
* @return an {@code int} value with a single one-bit, in the position
* of the highest-order one-bit in the specified value, or zero if
* the specified value is itself equal to zero.
* @since 1.5
*/
public static int highestOneBit(int i) {
// HD, Figure 3-1
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return i - (i >>> 1);
}
它做了什么事情呢?我们从 return
入手。
对于 0 0 0 1 X X X X
,最终会变成 0 0 0 1 1 1 1 1
,记做 i
。把 i
再右移一位变成 0 0 0 0 1 1 1 1
,然后两数做差。
i 0 0 0 1 1 1 1 1
i >>> 1 0 0 0 0 1 1 1 1
0 0 0 1 0 0 0 0
就得到了这个函数最后返回的结果了。
将 0 0 0 1 X X X X
变成 0 0 0 1 1 1 1 1
,可以通过复制实现。
第一步,将首位的 1
赋值给它的旁边。
i |= (i >> 1);
0 0 0 1 X X X X -> 0 0 0 1 1 X X X
现在首位有两个 1 了,所以就将这两个 1 看做一个整体,继续把 1 赋值给它的旁边。
i |= (i >> 2);
0 0 0 1 1 X X X -> 0 0 0 1 1 1 1 X
现在首位有 4 个 1 了,所以就将这 4 个 1 看做一个整体,继续把 1 赋值给它的旁边。
i |= (i >> 4);
0 0 0 1 1 1 1 X -> 0 0 0 1 1 1 1 1
其实到这里已经结束了,但函数中是考虑最坏的情况,类似于这种 1000000...00, 首位是 1, 有 31 个 0
我们可以把上边的源码直接放过来。
public boolean isPowerOfTwo(int n) {
if (n <= 0) {
return false;
}
int i = n;
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
i = i - (i >>> 1);
return i == n;
}
上边的解法是我开始想到的,下边的解法全部来自 这里,分享一下。
解法三
一直进行除以 2
,直到不是 2
的倍数。
public boolean isPowerOfTwo(int n) {
if (n == 0) return false;
while (n % 2 == 0) {
n /= 2;
}
return n == 1;
}
也可以改写成递归的形式。
public boolean isPowerOfTwo(int n) {
return n > 0 && (n == 1 || (n%2 == 0 && isPowerOfTwo(n/2)));
}
解法四
做过 191 题 的话,对一个 trick
应该有印象。
有一个方法,可以把最右边的 1
置为 0
,举个具体的例子。
比如十进制的 10
,二进制形式是 1010
,然后我们只需要把它和 9
进行按位与操作,也就是 10 & 9 = (1010) & (1001) = 1000
,也就是把 1010
最右边的 1
置为 0
。
规律就是对于任意一个数 n
,然后 n & (n-1)
的结果就是把 n
的最右边的 1
置为 0
。
也比较好理解,当我们对一个数减 1
的话,比如原来的数是 ...1010000
,然后减一就会向前借位,直到遇到最右边的第一个 1
,变成 ...1001111
,然后我们把它和原数按位与,就会把从原数最右边 1
开始的位置全部置零了 ...10000000
。
有了这个知识,我们看一下解法二列举的 2
的幂次,只有一个 1
,如果通过 n&(n-1)
,那么就会变成 0
了。
同样的 0
和 -2147483648
不符合上边的规则,需要单独考虑。又因为所有负数一定不是 2
的幂次,所以代码可以写成下边的样子。
public boolean isPowerOfTwo(int n) {
if (n <= 0) {
return false;
}
return (n & (n - 1)) == 0;
}
解法五
java
中还有一个方法 Integer.bitCount(n)
,返回 n
的二进制形式的 1
的个数。
public boolean isPowerOfTwo(int n) {
if (n <= 0) {
return false;
}
return Integer.bitCount(n) == 1;
}
同样的,我们学习一下 bitCount
的源码。
/**
* Returns the number of one-bits in the two's complement binary
* representation of the specified {@code int} value. This function is
* sometimes referred to as the <i>population count</i>.
*
* @param i the value whose bits are to be counted
* @return the number of one-bits in the two's complement binary
* representation of the specified {@code int} value.
* @since 1.5
*/
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
191 题 题目就是求二进制 1
的个数,其中解法三介绍的其实就是上边的解法,我把解释贴过来。
有点类似于 190 题 的解法二,通过整体的位操作解决问题,参考 这里-by-time-m-is-the-count-of-1’s-and-another-several-method-of-O(1)-time) ,也是比较 trick
的,不容易想到,但还是很有意思的。
本质思想就是用本身的比特位去记录对应位数的比特位 1
的个数,举个具体的例子吧。为了简洁,求一下 8
比特的数字中 1
的个数。
统计数代表对应括号内 1 的个数
1 1 0 1 0 0 1 1
首先把它看做 8 组,统计每组 1 的个数
原数字:(1) (1) (0) (1) (0) (0) (1) (1)
统计数:(1) (1) (0) (1) (0) (0) (1) (1)
每个数字本身,就天然的代表了当前组 1 的个数。
接下来看做 4 组,相邻两组进行合并,统计数其实就是上边相邻组统计数相加即可。
原数字:(1 1) (0 1) (0 0) (1 1)
统计数:(1 0) (0 1) (0 0) (1 0)
十进制: 2 1 0 2
接下来看做 2 组,相邻两组进行合并,统计数变成上边相邻组统计数的和。
原数字:(1 1 0 1) (0 0 1 1)
统计数:(0 0 1 1) (0 0 1 0)
十进制: 3 2
接下来看做 1 组,相邻两组进行合并,统计数变成上边相邻组统计数的和。
原数字:(1 1 0 1 0 0 1 1)
统计数:(0 0 0 0 0 1 0 1)
十进制: 5
看一下 「统计数」的变化,也就是统计的 1
的个数。
看下二进制形式的变化,两两相加。
看下十进制形式的变化,两两相加。
最后我们就的得到了 1
的个数是 5
。
所以问题的关键就是怎么实现每次合并相邻统计数,我们可以通过位操作实现,举个例子。
比如上边 4
组到 2
组中的前两组合成一组的变化。要把 (1 0) (0 1)
两组相加,变成 (0 0 1 1)
。其实我们只需要把 1001
和 0011
相与得到低两位,然后把 1001
右移两位再和 0011
相与得到高两位,最后将两数相加即可。也就是(1001) & (0011) + (1001) >>> 2 & (0011)= 0011
。
扩展到任意情况,两组合并成一组,如果合并前每组的个数是 n
,合并前的数字是 x
,那么合并后的数字就是 x & (000...111...) + x >>> n & (000...111...)
,其中 0
和 1
的个数是 n
。
public int hammingWeight(int n) {
n = (n & 0x55555555) + ((n >>> 1) & 0x55555555); // 32 组向 16 组合并,合并前每组 1 个数
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333); // 16 组向 8 组合并,合并前每组 2 个数
n = (n & 0x0f0f0f0f) + ((n >>> 4) & 0x0f0f0f0f); // 8 组向 4 组合并,合并前每组 4 个数
n = (n & 0x00ff00ff)+ ((n >>> 8) & 0x00ff00ff); // 4 组向 2 组合并,合并前每组 8 个数
n = (n & 0x0000ffff) + ((n >>> 16) & 0x0000ffff); // 2 组向 1 组合并,合并前每组 16 个数
return n;
}
写成 16
进制可能不好理解,我们拿16 组向 8 组合并举例,合并前每组 2 个数。也就是上边我们推导的,我们要把 (1 0) (0 1)
两组合并,需要和 0011
按位与,写成 16
进制就是 3
,因为合并完是 8
组,所以就是 8
个 3
,即 0x33333333
。
再回到 java
的源码。
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
第一步写法不一样,也很好理解。结合下边的图看,
第一步要做的就是把 11 -> 10
,01 - > 01
,00 -> 00
,10 -> 01
。
其实就是原来的数减去高位的数,11 - 1 = 10
, 01 - 0 = 01
,00 - 0 = 00
,10 - 1 = 01
。
第二步一致。
第三步,没有与完相加,而是先相加再相与,之所以可以这么做,是因为四位的话最多就是 4
个 1
,也就是 0100
和 0100
合并,结果最多也就是 4
位,所以只需要最后和 0f
相与,将高四位置零即可。
第四步和第五步没有相与,是因为最多 32
个 1
,用二进制表示就是 100000
,所以只需要低 6
位的结果,高 8
位是什么已经不重要了,也就不需要通过相与置零了,最后只需要和 0x3f(111111)
相与取得我们的结果即可。
解法六
前边提到对于 int
类型,最大的 2
的幂次是 2 的 30 次方,即1073741824
。对于 n
分两种情况讨论。
如果
n
是2
的幂次,那么,所以
。
如果
n
不是2
的幂次,那么,其中
j
是一个奇数,因为n
一直进行除以2
可以得到k
,直到不能被2
整除,此时一定是奇数,也就是公式中的j
。。
综上所述,通过是否能被 2
的 30
次方,即1073741824
整除,即可解决我们的问题。
public boolean isPowerOfTwo(int n) {
if (n <= 0) {
return false;
}
return 1073741824 % n == 0;
}
解法七
超级暴力打表法,因为 int
范围内 2
的幂次也只有 31
个数。
public boolean isPowerOfTwo(int n) {
return new HashSet<>(Arrays.asList(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608,16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824)).contains(n);
}
总
这道题的话使用了很多二进制的技巧,因为题目本身比较简单,所以就是各种大神秀操作了,哈哈,大概是解法最多的一道题了。
添加好友一起进步~
如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^
如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情