面试题-求一个数的二进制数中1的个数

5 篇文章 0 订阅
5 篇文章 0 订阅

某大型社交互联网公司暑期实习生一面有个面试官出了这么一道题目,求一个数的二进制数中1的个数,如何求呢。

我的想法是这样的,对于一个int类型的正数,占了4字节共32位,平时把这个数转成二进制数我是这样计算的,假设这个数为num,先找一个2的n次方的数,这个数刚刚好比待解的数大,然后2的n-1次方比这个数小,即2^n > num > 2^(n-1),那么我们就可以知道在num = 2^(n-1)+rest ,然后在剩下的部分用同样的方式,知道rest = 0。即用这种方式把这个数num分解成不同的2的幂的组合。

举个例子,7这个数,先找到2^3 = 8 ,比7大,即 2^3>7>2^2,所以 7 = 2^2 + 3 ,对于3 用同样的方式进行判断,先判断3是否大于2^(2 - 1)= 2^1,所以有 2^2>3>2^1,这个时候3 = 2^1+1,再判断1, 2^1>1>=2^0 , 所以1 = 1^1 + 0,此时rest = 0,因此 7 = 2^2+2^1+1 。即 7 的二进制数为 111,因此在程序中,我们可以维持一个32个元素的boolean数组,相应的位置置为true,其他位置默认都是false,所以在7这个数中,数组的第0喂和第1位、第2位都是true,其他都是false。这样就可以得到7的二进制数组。程序用递归比较简单,但是效率不高,所以考虑用while循环替代,在输出的时候是输出一个字符串,由于打印需要多次拼接字符串,所以用StringBuffer这个类来拼接,效率比较高。

程序如下:

/*	程序来源:面试题
 * 	源文件名称:NumToBin.java
 *	要  点:
 *		输入一个数,输出它二进制中1的个数
 */
 
public class NumToBin{
	public static void main(String[] args){
		System.out.println(numToBin(65531));
	}
	
	public static String numToBin(int num){
		
		int c_num = num;	//拷贝副本
		int a = 1;			//初始为2的0次方1
		int pow = 0;		//2的pow次方
		boolean bools[] = new boolean[32];  		//数组变量存储1的位置
		StringBuffer s = new StringBuffer("");   	//由于要循环拼接字符串使用缓冲字符串类效率高
		int flag = 0;								//存储1的个数
		
		
		//从2的0次方开始找,找到刚好2^pow > num > 2^(pow-1)的数a ,这里^表示幂,即2的pow次方
		while(num>a){
			pow++;
			a*=2;
		}
		
		//这里开始由a往后找,每次找到一个小于等于num的数,
		//则num减去这个数,然后对应二进制数组中位置置1
		//然后继续找直到num为0
		while(num>0){
			if(num < a){
				a/=2;
				pow--;
			}else{
				num = num - a;
				bools[pow] = true;
			}
		}
		
		//找到num的二进制数组中的第一个为1的位置
		int index = -1;
		for(int i = 31;i>=0;i--){
			if(bools[i] == true){
				index = i;
				break;
			}
		}
		
		//循环拼接二进制字符串
		if(index>=0)
			for(int i = index;i>=0;i--){
				if(bools[i] == true){
					s.append(1);
					flag++;
				}else{
					s.append(0);
				}	
			}
		
		if(c_num == 0)
			return c_num + "\t二进制为:" + 0000 + "\t总共 :"+ flag +"\t个1";
		else
			return c_num + "\t二进制为:" + s.toString()+ "\t总共 :"+ flag +"\t个1";
	}
}


运行结果如图:


当然还有一种情况是如果int类型的数可能是正数也可能是负数,要是个负数怎么办呢,我们先来看看负数在计算机里面是如何表示的,定义是这样的:

---------------------------------------------------------------------分割线----------------------------------------------------------------------------

引用自:http://wenwen.sogou.com/z/q127021346.htm

在2进制中,负数是以它正值的补码形式表达

具体来分析一下:

原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。
比如 0000 0000 0000 0000 0000 0000 0000 0101是 5的 原码。
反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。

取反操作指:原为1,得0;原为0,得1。(1变0; 0变1)

比如:将5的二进制表达式的每一位取反,得
1111 1111 1111 1111 1111 1111 1111 1010
称:1111 1111 1111 1111 1111 1111 1111 1010 是 0000 0000 0000 0000 0000 0000 0000 0101 的反码。

反码是相互的,所以也可称:
1111 1111 1111 1111 1111 1111 1111 1010 和 0000 0000 0000 0000 0000 0000 0000 0101 互为反码。
补码:反码加1称为补码。
也就是说,要得到一个数的补码,先得到反码,然后将反码加上1,所得数称为补码。
那么,5的补码为:

1111 1111 1111 1111 1111 1111 1111 1010 + 1 =
1111 1111 1111 1111 1111 1111 1111 1011

所以,-5 在计算机中的二进制表达为:
1111 1111 1111 1111 1111 1111 1111 1011
转换为十六进制:0xFFFFFFFB。

---------------------------------------------------------------------分割线----------------------------------------------------------------------------

所以其实很好解决,求一个负数在计算机中的二进制编码,我们只要求它的绝对值的数的反码再加上1,反码很好求,在我们可以求这个数的正数的二进制数组,我们只要按位取反就可以了,就到到反码的数组,然后还要加1,如何加1,其实可以在取反的时候同时进行,就是设置一个是否进位的标志位,初始化为true,从第一个开始判断,首先第一位是有进位的,因为要加1,这里有两种情况,第一种是如果第一位为0 ,那么取反后为1,然后接受进位加1,则变成0,然后再向前进位;第二种情况,是第一位为1,取反后为0,接受进位加1后为1,那么不需要进位了,则进位标志位false,然后第二位则根据进位标志为false知道不需要进位了。综合这两种情况,在有进位标志的情况下,其实相当于两次取反抵消,还是原来的值,所以直接判断原来的值是否为0,是则继续进位,否则不需要进位,具体代码如下(虚线内为在前一个版本中改动的):

/*	程序来源:腾讯面试题
 * 	源文件名称:NumToBin2.java
 *	要  点:
 *		输入一个数,输出它二进制中1的个数,包括负数版本
 */
 
public class NumToBin2{
	public static void main(String[] args){
		System.out.println(numToBin(-1));
		System.out.println(numToBin(15));
		System.out.println(numToBin(-15));
	}
	
	public static String numToBin(int num){
		
		//-----------------------------
		int c_num;
		if(num<0) {
			c_num = num;	//拷贝副本
			num = num*(-1);		//num小于0则取绝对值   
		}else
			c_num = num;	//拷贝副本
		//-----------------------------
		
		int a = 1;			//初始为2的0次方1
		int pow = 0;		//2的pow次方
		boolean bools[] = new boolean[32];  		//数组变量存储1的位置
		StringBuffer s = new StringBuffer("");   	//由于要循环拼接字符串使用缓冲字符串类效率高
		int flag = 0;								//存储1的个数
		
		
		//从2的0次方开始找,找到刚好2^pow > num > 2^(pow-1)的数a ,这里^表示幂,即2的pow次方
		while(num>a){
			pow++;
			a*=2;
		}
		
		//这里开始由a往后找,每次找到一个小于等于num的数,
		//则num减去这个数,然后对应二进制数组中位置置1
		//然后继续找直到num为0
		while(num>0){
			if(num < a){
				a/=2;
				pow--;
			}else{
				num = num - a;
				bools[pow] = true;
			}
		}
		
		//-----------------------------
		//这里如果要转化的数是负数,那么要求其补码
		//如果进位标志位为true,那么要判断当前位置是否还需要进位
		//如果取反之后再加上进位的1后为0,那么需要进位
		boolean carry_flag = true;
		if(c_num<0){
			for(int i = 0;i<31;i++){
				if(carry_flag){
					if(bools[i])
						carry_flag = false;
				}else bools[i] = !bools[i];
			}
			bools[31] = true;
		}
		//-----------------------------
		
		//找到num的二进制数组中的第一个为1的位置
		/*
		int index = -1;
		for(int i = 31;i>=0;i--){
			if(bools[i] == true){
				index = i;
				break;
			}
		}
		*/
		
		//循环拼接二进制字符串
		for(int i = 31;i>=0;i--){
			if(bools[i] == true){
				s.append(1);
				flag++;
			}else{
				s.append(0);
			}
		}
		
		if(c_num == 0)
			return c_num + "\t二进制为:" + 0000 + "\t总共 :"+ flag +"\t个1";
		else
			return c_num + "\t二进制为:" + s.toString()+ "\t总共 :"+ flag +"\t个1";
	}
}
运行结果如下:



自己的想法就是这样实现,暂时没有想到更简单的方法,如果有更巧妙简单的方法或者有什么错误的地方,请大家多多指导~~转载请注明出处。~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值