题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
将输入的整数分成0,正,负三种情况分别讨论。正数和0不必多说,当负数时,要将反码的32位用0补全,补码是取反加一,再求1的个数,所以可以不必求补码,直接转化成反码加0,1和0的代数关系进行调换,即1 + 0 = 0, 0 + 0进一位0原位写1,最终求0的个数,代码如下:
解法一:
public class Solution {
public int NumberOf1(int n) {
if(n == 0) {
return 0;
}
if(n > 0) {
return NumberOf1(n / 2) + n % 2;
}
int num = 0;
if(n < 0) {
if(n == Integer.MIN_VALUE) {
return 1;
}
n = -n;
StringBuffer sb = new StringBuffer();
int flag = 1;
while(n > 0) {
sb.append(n % 2);
n /= 2;
}
while(sb.length() < 32) {
sb.append("0");
}
for(char c : sb.toString().toCharArray()) {
if(c == '0') {
if(flag == 0) {
num++;
}
else {
flag = 1;
}
}
else {
if(flag == 1) {
num++;
flag = 0;
}
}
}
}
return num;
}
}
运行时间:16ms
占用内存:9404k
要记得考虑到当整数位Integer.MIN_VALUE时,其补码为1加上31个0,直接返回1即可。
不过想来这种解法实在繁琐,进行了整数转化为二进制,再进行二进制的计算两步,但是否可以直接用整数的二进制进行位运算呢,于是有了如下的解法。
解法二:递归
public class Solution {
public int NumberOf1(int n) {
if(n == 0) {
return 0;
}
return NumberOf1(n & (n - 1)) + 1;
}
}
在二进制的位面看待这些数,n - 1即是找到最后面的一个1,并将其置0,这个1后面的0全部置1,结果与n相与后刚刚置为1的那些位又重新变成0,即n & (n - 1)是将n的最后一个1找到并置0,其他位置不变,那么这个操作可以递归调用多少次,n的二进制表示就有多少个1。
运行时间:14ms
占用内存:9288k
然而递归的实现是通过调用函数本身,函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的。具体是每次调用函数本身要保存的内容包括:局部变量、形参、调用函数地址、返回值。那么,如果递归调用N次,就要分配N*局部变量、N*形参、N*调用函数地址、N*返回值。这势必是影响效率的。
以上文字引用自其他人的博客 。
因为递归使得效率低于普通的循环调用,简单问题能不用递归就不用递归,将解法二进行改进↓
解法二改进:循环
public class Solution {
public int NumberOf1(int n) {
int res = 0;
while(n != 0){
res++;
n = n & (n - 1);
}
return res;
}
}
运行时间:12ms
占用内存:9272k
继续利用位运算,题目要求找到有多少个1,那么想到设置一个mask,这个mask的二进制表示只有一个1,且这个1从最低位一直移动到最高位,每次移动之后与n进行与运算,结果不为0的次数就是所求1的个数。
解法三:
public class Solution {
public int NumberOf1(int n) {
int res = 0;
int mask = 1;
for(int i = 0; i < 32; i++) {
if((mask & n) != 0) {
res++;
}
mask <<= 1;
}
return res;
}
}
运行时间:12ms
占用内存:9280k
方法总结:
&:位的与运算
<<:左移一位