前言
想解决这个问题首先要了解一些位运算符和移位运算符的用法以及整数在内存中是如何存储的,如果·你已经了解这些可直接跳过。下面介绍右移运算符>>、无符号右移运算符>>>和按位与运算符&以及补码的知识。整数在内存中存储的是其二进制的形式,大小是4个字节,也就是32个比特位,这些位运算符和移位运算符都是在数的位这个单位上做运算的。
1.整数在内存中的存储方式
整数在内存中是以其补码的形式进行存储的,整数可分为负数和非负数两部分,负数的补码和非负数的补码是不同的。整数在内存中的32个比特位分为数值位和符号位,最高的位不表示数值而是代表着正负,0为正数,1为负数。
(1)非负数的补码
非负数的补码就是其二进制形式。例如7的二进制为0111,所以7的补码为0000…000111(共32位)。在内存中的存储如下图所示:
(2)负数的补码
负数的补码需要经过三步得到,首先得到它绝对值的二进制,然后二进制按位取反,最后+1。例如求-7的补码过程:
1.首先得到他绝对值的二进制:
0000…000111
2.再按位取反
1111…111000
3.最后+1
1111…111001
这就是-7的补码,-7在内存中的存储如下图所示:
2.位运算符&和移位运算符>>>、>>简介
(1)按位与&
我们知道1&1=0,1&0、0&1和0&0都等于0。按位与就是把整数的补码中对应的每一位进行与运算。
例如11&6的结果就是2。
(2)右移运算符>>和无符号右移运算符>>>
这两个运算符都很简单,右移运算符>>是把整数在内存中的补码向右移动一个比特位,最右边的比特位扔掉,最左边补符号位。无符号右移运算符>>>也是右移一个比特位,不同的是最左边补的是0。
获取一个数二进制的1的个数
解决此问题大概有三种方法,后一个方法是前一个方法的优化。
1.
分别把32位的每一位&1从而判断此位是否为1
import java.util.Scanner;
public class demon1 {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int count = 0;//记录n的二进制位中的1的个数
int n = sc.nextInt();
for (int i = 0; i < 32; i++) {//循环32此每次得到n的一个位
if(((n >> i) & 1) == 1){ //如果从右往左数第i+1位为1则count++
count++;
}
}
System.out.println(count);
}
}
2.
第一种方法无论n二进制中有几个1,循环都会执行32次。如果我们每次向右移一位都判断结果是否为0,然后再&1判断此位是否1,这样程序执行时间会更少。例如对于7,第一种方法要执行32次循环而第二种方法只要执行3次即可。需要注意的是这里的移位运算符就不能用>>,而必须用>>>,因为对于负数,右移运算符>>的运算结果会在左侧补1,这样结果永远不会为0,将陷入死循环。代码如下:
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int count = 0;//记录1的个数
int n = sc.nextInt();
int i = 0;
//当n无符号右移i位不等于0才进行判断第i+1位是否为1,否则不用再进行判断
while((n >>> i) != 0){
if(((n >>> i) & 1) == 1){
count++;
}
i++;
}
System.out.println(count);
}
3
这一种方法会比第二种方法更快,n中有几个1循环就会执行多少次,如果一个数的补码是111000…000
那么第二种方法和第一种方法循环都要执行32次,而第三种方法只需执行3次。首先我先介绍一个规律:假设有一个整数x,x&(x-1)的结果中的1的个数会比x补码中1的个数少1。那要求n中补码中1的个数,我们只需判断n是否为0,不为0说明n的补码中有1,将n&(n-1结果再赋值给n继续判断n是否为0,直到n为0。代码如下:
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int count = 0;
int n = sc.nextInt();
while(n != 0)
{
count++;
n = n & (n - 1);
}
System.out.println(count);
}