【面试题】一文打尽位运算相关题目

二进制中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
 00000010 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;
    }
}

找出数组中只出现一次的元素

在这里插入图片描述
异或运算有以下三个性质:

  1. 任何数和 0 做异或运算,结果仍然是原来的数,即 a XOR 0 = a
  2. 任何数和其自身做异或运算,结果为 0,即 a XOR a = 0
  3. 异或运算满足交换律和结合律,即 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;
    }
}

找出数组中两个只出现一次的元素

在这里插入图片描述

  1. 首先对所有的元素进行异或操作,得到即为这两个不重复元素的异或结果。因为是不同的两个数字,所以这个值必定不为0。
  2. 取这个异或结果最低的那一位 1(最右边的1)作为位掩码 mask,因为这两个不同的元素在该位上肯定不同。
  3. 通过将原数组与这个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;
    }
}

参考

Leetcode 题解 - 位运算

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值