二进制中1的个数
解法一:逐位判断
先判断输入的整数⼆进制表示中最右边⼀位是不是1,接着把整数右移⼀位,原来处于从右边数起的第⼆位被移到最右边了,再判断该位是不是1。 这样每次右移⼀位, 直到整个整数变成0为止。
怎么判断⼀个整数的最右边是不是1?我们知道,整数 1 除了最右边⼀位之外所有位都是0。 因此,如果⼀个整数与1做与运算的结果是1,则表示该整数最右边⼀位是1,否则是0。
Python
class Solution(object):
def hammingWeight(self, n):
"""
:type n: int
:rtype: int
"""
count = 0
while n:
if n & 1: # 如果与1做&运算结果不为0,则表示最右边一位是1
count += 1
n = n>>1 # 右移一位
return count
Java
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
while(n!=0){
if ((n & 1) == 1) // 比较运算符优先级比位运算符高!
count++;
n >>>= 1; // 无符号右移
}
return count;
}
}
解法二:掩码位移法
解法一存在一个隐患,若输入为负的有符号数时,移位后仍然要保证是个负数,因此最高位会补 1。如果一直做右移运算,最终这个整数会变成每个位上全为1的0xFFFFFFFF,永远不会满足等于0的条件,陷入死循环。
改进方法是,我们可以不右移输入的数字,而改为对位掩码进行左移操作,本题中掩码一开始设为数字1,把数字 n与 1做与运算,判断 n的最低位是否为 1,接着把 1左移一位得到 2,再和 i做与运算,就可以判断 n的次低位是否为 1,…以此类推,反复左移,每次都可以判断 n的其中一位是不是为 1。
Python
class Solution(object):
def hammingWeight(self, n):
"""
:type n: int
:rtype: int
"""
count = 0
mask = 1
for i in range(32): # 无符号整型长度为32位
if n & mask:
count += 1
mask<<= 1 # 位掩码左移一位
return count
Java
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
int mask = 1;
for (int i=0; i<32; i++){
if ((n & mask) != 0)
count++;
mask <<= 1; // 位掩码左移
}
return count;
}
}
解法三:巧用 n&(n−1)
总结起来就是:把一个整数减去 1,再和原来该整数做与运算,会把该整数最右边的一个 1变成 0,那么一个整数的二进制表示中有多少个 1,就可以进行多少次这样的操作。
Python
class Solution(object):
def hammingWeight(self, n):
"""
:type n: int
:rtype: int
"""
count = 0
while n:
count += 1
n = n&(n-1)
return count
Java
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
while (n != 0){
count++;
n = n&(n-1);
}
return count;
}
}
此外,可以使用 Java中提供的位操作。
static int Integer.bitCount(); // 统计 1 的数量
static int Integer.highestOneBit(); // 获得最高位
static String toBinaryString(int i); // 转换为二进制表示的字符串
2的幂
一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位为1,其他位上都是0。把这个整数减去1的结果和它自身做与运算,这个整数中唯一的1就会变成0,也就是说 n & (n-1) 一定为 0。
另外,注意 2的整数次方一定是大于 0的。
Python
class Solution(object):
def isPowerOfTwo(self, n):
"""
:type n: int
:rtype: bool
"""
return n>0 and n&(n-1)==0
Java
class Solution {
public boolean isPowerOfTwo(int n) {
return n>0 && ((n & (n-1)) == 0);
}
}
4的幂
4 的幂和 2 的幂的相同点是二进制表示中只有一位为 1,不同点是 2 的幂中这个 1 处于偶数位置上, 而 4 的幂中这个 1 处于奇数位置上。
因此,将 4 的幂与二进制数 (101010…10)2相与会得到 0。
Python
class Solution(object):
def isPowerOfFour(self, num):
"""
:type num: int
:rtype: bool
"""
return num>0 and num&(num-1) == 0 and num&(0b10101010101010101010101010101010) == 0
Java
class Solution {
public boolean isPowerOfFour(int num) {
return num>0 && (num&(num-1)) == 0 && (num & 0b10101010101010101010101010101010) == 0;
// 或者最后一个条件写成 num & 0xaaaaaaaa == 0
}
}
颠倒二进制位
解法一:取模求和
想来思考,反转一个十进制整数是怎么操作的,方法是这样的ans = ans * 10 + n % 10; n = n / 10;
同理,类比到一个二进制整数,那就是ans = ans * 2 + n % 2; n = n / 2;
这样的方法对于题目要求的无符号整数或者对于正整数来说,是没有问题的。但是像Java这样的语言中,是没有提供无符号整数类型的。在这种情况下,输入和输出都将被指定为有符号整数类型。这种情况下,对于有符号整型,仅仅使用这种写法是会产生问题的。
- 比如发生整型溢出的时候,Java中溢出后的二进制会变成负数(补码表示),Java中负数执行
/2
操作会向零取值(-3 / 2= -1),而如果使用位操作符,-3 >> 1 = -2
。 - 再比如,我们需要每次取原来的最右边一位的值,不断添加到新结果的后边从而实现反转的效果,对于负数的操作,
-3 % 2= -1
不能取到我们希望的最右一位上的数字1,而如果使用位操作符-3 & 1 = 1
,则取到了该位上的数值。 - 因此,应该使用位运算来避免溢出问题,并限制循环次数为题目所要求的的无符号整型长度(32位)。
- 此外,还要考虑前导零的问题,因为对于十进制是不需要考虑翻转后前面的0,比如100反转后就是1,不必写成001,而对于二进制数,需要保留前导零。
Python
class Solution:
# @param n, an integer
# @return an integer
def reverseBits(self, n):
res = 0
for i in range(32):
res = (res << 1) + (n & 1)
n = n >> 1
return res
Java
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
int res = 0;
for (int i=0; i<32; i++){
res = (res << 1) | n&1; // 按位或操作其实就相当于按位相加,即等同于 (res << 1) + (n&1)
n >>= 1; // 右移,相当于 n=n/2
}
return res;
}
}
解法二:直接逐位反转
直接颠倒计算每一位的数字,对于一个32位的整型(索引范围【0~31】),如果 n 的第 i 位为1,则相应的res 中第 31 - i 位应该为1;类似的,如果 n 的第 i 位为0,则相应的res 中第 31 - i 位应该为0。
Python
class Solution:
# @param n, an integer
# @return an integer
def reverseBits(self, n):
res = 0
for i in range(32):
if (n & (1<<i)) != 0:
tmp = 1 << (31-i)
else:
tmp = 0
res = res | tmp
return res
Java
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
int res = 0;
int tmp;
for (int i=0; i<32; i++){
if ((n & (1<<i)) != 0)
tmp = 1 << (31-i);
else
tmp = 0;
res = res | tmp; // 等同 res = res + tmp
}
return res;
}
}
解法三:分治法
既然知道 int 值一共32位,那么可以采用分治思想,反转左右16位,然后反转每个16位中的左右8位,依次类推,最后反转2位,反转后合并即可,利用位运算可以实现在原地反转。
// 原数字43261596
0000 0010 1001 0100 _ 0001 1110 1001 1100
// 反转左右16位:
0001 1110 1001 1100 _ 0000 0010 1001 0100
// 继续分为8位一组反转:
1001 1100 0001 1110 _ 1001 0100 0000 0010
// 4位一组反转:
1100 1001 1110 0001 _ 0100 1001 0010 0000
// 2位一组反转:
0011 1001 0111 1000 _ 0010 1001 0100 0000
// 最后得到的就是43261596反转后的结果:964176192
Python
class Solution:
# @param n, an integer
# @return an integer
def reverseBits(self, n):
n = (n >> 16) | (n << 16)
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8)
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4)
n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2)
n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1)
return n
Java
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
n = (n >>> 16) | (n << 16);
n = ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8);
n = ((n & 0xf0f0f0f0) >>> 4) | ((n & 0x0f0f0f0f) << 4);
n = ((n & 0xcccccccc) >>> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xaaaaaaaa) >>> 1) | ((n & 0x55555555) << 1);
return n;
}
}
汉明距离
对两个数进行按位异或操作,位级表示不同的那一位为 1,统计异或结果有多少个 1 即可。
Python
class Solution(object):
def hammingDistance(self, x, y):
"""
:type x: int
:type y: int
:rtype: int
"""
z = x ^ y
count = 0
while z:
if z & 1 == 1:
count += 1
z >>= 1
return count
Java
class Solution {
public int hammingDistance(int x, int y) {
int z = x ^ y; // 按位异或
int count = 0;
while (z != 0){ // 统计异或结果有多少个 1
if ((z & 1) == 1)
count++;
z = z >>> 1; // 无符号右移。无论是正数还是负数,高位通通补0。
}
return count;
}
}
巧用 n&(n−1)
Python
class Solution(object):
def hammingDistance(self, x, y):
"""
:type x: int
:type y: int
:rtype: int
"""
z = x ^ y
count = 0
while z:
count += 1
z = z & (z-1)
return count
Java
class Solution {
public int hammingDistance(int x, int y) {
int z = x ^ y; // 按位异或
int count = 0;
while (z != 0){ // 统计异或结果有多少个 1
count++;
z = z & (z-1);
}
return count;
}
}
找出数组中缺失的那个数
我们可以想象,如果将0,1,2,3……n-1,n都放入数组中,那么数组中除了目标元素只出现了一次,其余元素都出现了两次,问题就变成了在数组中找到只出现了一次的数。
在编写代码时,由于 [0…n] 恰好是这个数组的下标加上 n (即数组长度),因此可以用一次循环完成所有的异或运算。
Python
class Solution(object):
def missingNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
res = len(nums)
for i in range(len(nums)):
res ^= i ^ nums[i];
return res
Java
class Solution {
public int missingNumber(int[] nums) {
int res = nums.length;
for (int i=0; i<nums.length; i++){
res ^= i ^ nums[i];
}
return res;
}
}
找出数组中只出现一次的元素
异或运算有以下三个性质:
- 任何数和 0 做异或运算,结果仍然是原来的数,即
a XOR 0 = a
- 任何数和其自身做异或运算,结果为 0,即
a XOR a = 0
- 异或运算满足交换律和结合律,即
a XOR b XOR a = a XOR a XOR b = 0 XOR b = b
两个相同的数异或的结果为 0,对所有数进行异或操作,根据性质3,可以先对 n-1个出现2次的元素结合进行异或,得到 n-1个0,再与剩余的那个单独的元素进行异或,得到的结果就是单独出现的那个数。
Python
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
res = 0
for i in range(len(nums)):
res = res ^ nums[i]
return res
Java
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int i:nums){
res ^= i;
}
return res;
}
}
找出数组中两个只出现一次的元素
- 首先对所有的元素进行异或操作,得到即为这两个不重复元素的异或结果。因为是不同的两个数字,所以这个值必定不为0。
- 取这个异或结果最低的那一位 1(最右边的1)作为位掩码 mask,因为这两个不同的元素在该位上肯定不同。
- 通过将原数组与这个mask进行与操作,结果为 0的分为一个数组,为1的分为另一个数组。这样就把问题转换成:在两个子数组中找出只出现一次的元素。对这两个子问题分别进行全异或就可以得到两个解,也就是这两个不重复的元素了。
Python
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
tmp = 0
for num in nums:
tmp ^= num;
mask = tmp & (-tmp)
a,b = 0,0
for num in nums:
if num & mask:
a ^= num
else:
b ^= num
return [a,b]
Java
class Solution {
public int[] singleNumber(int[] nums) {
// 对数组所有元素进行异或,得到两个不重复元素的异或结果
int tmp = 0;
for (int num:nums){
tmp ^= num;
}
// 取到异或结果最低的一位 1
int mask = tmp & (-tmp);
// 分治法,分别在两个子数组中找出两个数
int[] res = new int [2];
for (int num:nums){
if ((num & mask) == 0)
res[0] ^= num;
else
res[1] ^= num;
}
return res;
}
}
找出数组中唯一的数字(其它数字均出现3次)
统计所有数字的各二进制位中 1 的出现次数,并对 3 求余,结果则为只出现一次的数字
Java
// 本算法同样适用于数组nums中存在负数的情况
class Solution {
public int singleNumber(int[] nums) {
if(nums.length==0) return -1; // 输入数组长度不符合要求,返回-1;
int[] bitSum = new int[32]; // java int类型有32位,其中首位为符号位
int res = 0;
for(int num:nums){
int bitMask=1; //需要在这里初始化,不能和 res一起初始化
for(int i=31;i>=0;i--){
/*
bitSum统计所有数字的二进制每一位上的 1出现个数,bitSum[0]为符号位
这里同样可以通过对 num的无符号右移>>>来实现,而带符号右移(>>)左侧会补符号位,对于负数会出错。
但是不推荐这样做,最好不要修改原数组nums的数据
这里通过左移位掩码来做,左移则没有无符号、带符号的区别,都是在右侧补0
*/
if((num&bitMask)!=0) bitSum[i]++; // 这里判断条件也可以写为(num&bitMask)==bitMask,注意不是==1
bitMask = bitMask<<1;
}
}
for(int i=0;i<32;i++){
/*
利用左移操作和或运算,将bitSum数组中各二进位的值恢复到数字 res上
这种做法使得本算法同样适用于负数的情况
注意下面这两步顺序不能变,否则最后一步会多左移一次
*/
res = res<<1;
res |= bitSum[i]%3; // 也可以res += bitSum[i]%3
}
return res;
}
}
判断一个数是否为交替位二进制数
凡是符合题目中的交替位二进制,将其错位异或的结果必然全是1,所以将错位异或的结果+1后,就会得到只有一位为1的二进制数,再用 n ^ (n-1) 进行检查即可。
Python
class Solution(object):
def hasAlternatingBits(self, n):
"""
:type n: int
:rtype: bool
"""
res = n ^ (n >> 1)
res += 1
return res & (res-1) == 0
Java
class Solution {
public boolean hasAlternatingBits(int n) {
int res = (n ^ (n>>1)) + 1;
return (res & (res-1)) == 0;
}
}
不用额外变量交换两个整数
a = a ^ b;
b = a ^ b;
a = a ^ b;
或者其实可以直接通过加减法实现
a = a + b;
b = a - b;
a = a - b;
不用加减乘除做加法
Java
class Solution {
public int add(int a, int b) {
// 无进位和 与 异或运算 规律相同,进位 和 与运算 规律相同(并需左移一位)
while(b != 0) { // 当进位为 0 时结束循环
int c = (a & b) << 1; // 计算进位 c
a = a^b; // a 存放非进位和
b = c; // b 存放进位 c
}
return a;
}
}