【题15 二进制中1的个数】Java实现

本文深入探讨了位运算的基本概念,包括与、或、异或、左移和右移等运算,并详细解析了如何通过位运算计算二进制中1的个数。文章提供了三种不同的算法实现,对比了它们的效率,并介绍了如何判断一个数是否为2的幂以及计算两个数之间的二进制差异。

位运算

位运算时把数字用二进制表示之后,对每一位上0或者1的运算。

二进制:
指数字的每一位都是0或者1.
二进制种有五种运算:与、或、异或、左移和右移
在这里插入图片描述
左移运算符m << n:
表示把m左移n位。左移n位的时候,最左边的n位将被丢弃。同时在最右边补上n个0.比如
在这里插入图片描述
右移运算符m >> n:
表示把m右移n位。右移n位的时候,最右边的n位将被丢弃。
如果数字是一个无符号的数值,则用0填补最左边的n位。
如果数字是一个有符号的数值,则用数字的符号位填补最左边的n位。
(有符号的类型,1表示负数,0表示整数。右移时负数最左边补充1,正数补充0)
也就是说
如果数字原先是一个正数,则右移之后在最左边补n个0;
如果数字原先是一个负数,则右移之后在最左边补n个1;
在这里插入图片描述
【题15 二进制中1的个数】
【题目】
请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。
例如:
把9表示成二进制是1001,有2位是1.因此,如果输入9,则该函数输出2.

可能引起死循环的解法:
基本思路:
(1)先判断整数二进制表示中最右边一位是不是1;
(2)接着把输入的整数右移一位,此时原来处于从右边起的第二位被移到最右边了,再判断是不是1.
(3)这样每次移动一位,直到整个整数变成0位置。
问题变成了:如何判断一个整数的最右边是不是1.
只要把整数和1做位与运算看结果是不是0就知道了。
如果一个整数与1做与运算的结果是1,则表示该整数最右边一位是1,否则是0.
问题1:可以把右移运算换成除以2么?
回答:不可以,因为除法效率比移位运算要低得多。在实际编程中,应尽可能地用移位运算符代替乘除法。
问题2:上面的函数如果输入一个负数。比如,0x80000000,则运行的时候会发生什么情况?
回答:当把负数0x80000000右移一位的时候,并不是把最简单地最高位的1移到第二位变成0x40000000,而是0xC0000000。这时因为移位前是负数,仍然要保证移位后是一个负数因此移位后的最高位会设为1。如果一直做右移运算,那么最终这个数字就会变成0xF0000000而陷入死循环。

常规解法:
为避免死循环,不右移输入数字n,
(1)把n和1做与运算,判断n的最低位是不是为1。
(2)把1左移一位得到2,再和n做与运算,就能判断n的次低位是不是1……
(3)这样反复左移,每次都能判断n的其中一位是不是1.
在这个解法中,循环次数等于整数二进制的位数,32位的整数需要循环32次。

带来惊喜的解法
整数中有几个1就只需要循环几次
分析把一个整数减去1的情况。

  • 如果一个整数不等于0,那么该整数的二进制表示中至少有一位是1.
    假设这个数的最右边一位是1,那么减去1时,最后一位变成0而其他所有位都保持不变。也就是最后一位相当于做了取反操作,由1变成0.
  • 假设最后一位不是1而是0,如果该整数的二进制表示中最右边的1位于第m位,那么减去1时,第m位由1变成0,而第m位之后的所有0变成1,整数中第m位之前的所有位保持不变。

例如:
一个二进制数1100,它的第二位是从最右边数起的一个1.减去1后,第二位变成0,它后面的两位0变为1,前面的1保持不变,得到结果1011.

总结:
把一个整数减去1,再和原整数做与运算,会把该整数最右边的1变成0,那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。
实现(想计算一下每个循环的次数,但好像有些问题)

ackage ti15;

public class Lian {
	//法一
	public int fun(int num){
		int count = 0;
		int times = 0;
		while(num != 0){
			if((num & 1) == 1){
				count ++;
				times = times + 1;
			}
			num = num >>1;
		}
		System.out.println("循环"+times+"次");
		return count ;
	}
	
	//法二
	public int fun1(int num){
		int count = 0;
		int flag = 1;
		int times1 = 0;
		while(flag != 0){
			if((flag & num) != 0){
				count++;
				times1 = times1 + 1;
			}
			flag = flag <<1;
		}
		System.out.println("循环"+times1+"次");
		return count;
	}
	
	//法3
	public int fun2(int num){
		int count = 0;
		int times2 = 0;
		while(num != 0){
			count ++;
			times2 = times2+1;
			num = num & (num -1);
		}
		System.out.println("循环"+times2+"次");
		return count;
	}
	public static void main(String[] args) {
		Lian lx = new Lian();
		int n = 9;
		System.out.println(lx.fun(n));//2
		System.out.println(lx.fun1(n));//2
		System.out.println(lx.fun2(n));//2
		System.out.println("*********");
		int n1 = 0x7FFFFFFF;
		System.out.println(lx.fun(n1));//31
		System.out.println(lx.fun1(n1));//31
		System.out.println(lx.fun2(n1));//31
		System.out.println("***********");
		//int n2 = 0x80000000;
		//System.out.println(lx.fun(n2));
		int n3 = 12;
		System.out.println(lx.fun(n3));
		System.out.println(lx.fun1(n3));
		System.out.println(lx.fun2(n3));
		System.out.println("*********");
	}
	
}

【相关题目】
1.用一条语句判断一个整数是不是2的整数次方。一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位是1,而其他所有位都是0。根据前面分析,把这个整数减去1之后再和它自己做与运算。这个整数中唯一的1就会变成0.

2.输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n。比如10的二进制表示为1010,13的二进制表示为1101,需要改变1010中3位才能得到1101.我们可以分为两步解决这个问题:第一步求这俩个数的异或;第二步统计异或结果中1的位数。

【举一反三】
把一个整数减去1之后再和原来的整数做位于运算,得到的结果相当于把整数的二进制表示中最右边的1变成0.很多二进制的问题都可以用这种思路解决。

参考:
1.《剑指offer》
2.https://blog.youkuaiyun.com/weixin_37672169/article/details/80148443

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值