有为有不为,知足知不足
锐气藏于胸,和气浮于面
才气见于事,义气施于人
— 丰子恺
业精于勤…
题目描述:
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
Leecode 231 该题在小米公司,微软公司都曾被面过。
但是该题在面试过程中,真的不容易想到最优解。不过算法是一步一步演进的,不可能一步到位。
1. 普通解法
public boolean isPowerOfTwo(int n) {
if(n <= 0) return false;
while(n %2 == 0){
n = n >> 1;
}
return n == 1;
}
很显然,时间复杂度为O(logN)
2. 最优解
public boolean isPowerOfTwo(int n) {
if (n <= 0) return false;
return (n & (n - 1)) == 0;
}
关键点在于:(n & (n-1) )
public boolean isPowerOfTwo(int n) {
if (n == 0) return false;
return (n & (-n)) == n;
关键点在于:(n & (-n) )
3.为何最优?
对于刷题有一定经验的人,见到2立刻就会联想到位运算。
二进制中除了最右边的的1,其它位置上的1都表示2.
n | 原码 | 反码 | 补码 |
---|---|---|---|
1 | 1 | 0 | 1 |
2 | 10 | 01 | 11 |
3 | 011 | 100 | 101 |
4 | 100 | 011 | 100 |
5 | 101 | 010 | 011 |
3.1 知识储备
- 负整数是以补码形式存储的;
- 补码 = 反码 + 1 ;
- 反码 = 原码按位取反;
PS:对于原码,反码,补码不太清楚的,请自行学习了解。
思路回到这道题上,判断一个数是否是2的幂,这就需要我们找一下规律啦。在现实的面试中,面试官是会给我们一定的思考时间的。
n | 原码 |
---|---|
1 | 1 |
2 | 10 |
4 | 100 |
8 | 1000 |
我们可以发现:如果是2的幂,那么其二进制表示中有且仅有一个1,有0的话后面会跟一些0。
换个写法,我们会发现更多的秘密。
n | 原码 |
---|---|
1 | 00000001 |
2 | 00000010 |
4 | 00000100 |
8 | 00001000 |
这样更符号在计算机中的存储规律,同时我们也发现:1的位置总是出现在最右边
那么题目就可以转化为求最右边的1。
这里想一下原码,补码之间的相同点其实就是最右边的1.
n | 原码 | 补码 | 反码 | 原码&补码 |
---|---|---|---|---|
1 | 00000001 | 11111111 | 11111110 | 00000001 |
2 | 00000010 | 11111110 | 11111101 | 00000010 |
3 | 00000011 | 11111101 | 11111100 | 00000010 |
4 | 00000100 | 11111100 | 11111011 | 00000100 |
8 | 00001000 | 11111000 | 11110111 | 00001000 |
注意观察: n & (-n) 就会保留最右边的1,同时将其它位设置为0
PS : 这里列举出3,就是为了 最后的公式:n & (-n ) == n
最方便的其实是看这个:
n | 原码 | 补码 |
---|---|---|
1 | 1 | 1 |
2 | 10 | 10 |
4 | 100 | 100 |
8 | 1000 | 1000 |
- 反码 = 原码按位取反;
- 补码 = 反码 + 1 ;
- n & (-n) 会保留最右边的1,同时将其它位设置为0
- 我们还可以发现:2的幂的数原码 和 补码 一样。
3.2 延伸解法
根据上面的条件,我们肯定会猜想:
能不能把最右边的1给消灭掉?
事在人为嘛,因为二进制减一可以将最右边的1变成0,且右边的0变为1。
n | 原码(二进制) | n-1的二进制 | n & (n-1) |
---|---|---|---|
1 | 00000001 | 00000000 | 00000000 |
2 | 00000010 | 00000001 | 00000000 |
3 | 00000011 | 00000010 | 00000010 |
4 | 00000100 | 00000011 | 00000000 |
8 | 00001000 | 00000111 | 00000000 |
我们都知道:
2的幂的数1在最右边,后面跟的全是0.
这样的话,刚好跟上面的操作是互斥操作。
我们可以就可以采用 n & (n-1) == 0 来作为判断条件。
4. 总结一下
- 与2相关的算法题,可以优先考虑位运算。
- 负整数在计算机中以补码形式表示;
- n & (-n) 可以获取到二进制中最右边的 1,且其它位设置为 0;
- n-1 可以将最右边的 1 设置为 0,并且将右边其它位设置为 1;
- n & (n-1) 可以将最右边的 1 设置为 0;
Good Luck ~~