面试中常见的位运算题目

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Kaiyang_Shao/article/details/89554132

0. 常见位运算及操作简介

  • & : 与运算,两个数全为1结果为1,否则结果为0
  • | : 或运算,两个数全为0结果为0,否则结果为1
  • ~:非运算,1变为0,0变为1
  • ^:异或运算,相同为0,相异为1
  • " >>" 和 “>>>” , 算术右移和逻辑右移,相当于除以2

A = 11111111111111111111111110000001
B = 2
A >> B = 11111111111111111111111111100000
A >>> B = 00111111111111111111111111100000

  • “<<” 相当于乘以2
  • 技巧一:n & (n-1) 去掉最后一位的1
  • 技巧二: a ^ b ^b = a
  • 技巧三: n & (-n) 得到最后一位的1

1. 求二进制中1的个数

题目:给出一个整数n,求其二进制表示中1的个数。
思路:1.每次根据技巧一去掉一位1,进行统计;2.定义一个mask与原数的每一位进行与运算

// 求一个数中1的个数
    public static int numberOfOne1(int n){
        // 此方法会改变输入数据的值
        int cnt = 0;
        while(n != 0){
            cnt++;
            //去掉最后一个1
            n &= n-1;
        }
        return cnt;
    }

    //求一个数中1的个数
    public static int numberOfOne2(int n){
        // 此方法不会改变输入数据值
        int cnt = 0;
        int mask = 1;
        while(mask != 0){
            if((n & mask) != 0){
                cnt ++;
            }
            mask <<= 1;
        }
        return cnt;
    }

2. 判断一个数是否为2的幂或4的幂

题目:判断一个数是否为2的幂或者为4的幂
思路:1.如果为2的幂则二进制表示中1仅可出现一次;2.如果为4的幂,首先是2的幂,然后需要保证二进制中1的位置在偶数位上;

//判断一个数是否是2的幂
    public static boolean isPowOfTwo(int n){
        return (n != 0 && (n & (n - 1)) == 0);
    }

    //判断一个数是否是4的幂
    public static boolean isPowOfFour(int n){
        //先判断是否是2的幂
        //然后判断1所在的位置是否在偶数位上
        return ((n & (n - 1)) == 0) && n >0 && ((n & 0x55555555) != 0);
        //数学理论如下
        //依据 #1: (4^n-1) = (4-1) (4^(n-1) + 4^(n-2) + 4^(n-3) + ….. + 4 + 1)
        //依据 #2 4^(n+1) - 1 = 4*4^n -1 = 3*4^n + 4^n-1
        // return n > 0 && (n & (n - 1)) == 0) && ((n - 1) % 3 == 0);
    }

3. 位运算实现两数交换

问题:在不借助辅助变量的前提下进行两个变量之间的交换
思路:1.采用异或运算进行;2.采用 加减法运算进行

//位运算实现两数交换,在不使用中间变量前提下
    public static void change(int a , int b){
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        // 使用加法实现
//        a = a + b;
//        b = a - b;
//        a = a - b;
		// 使用减法实现
		  a = a - b;
		  b = a + b;
		  a = b - a;
    }

4. 找出唯一出现一次的数(1个数版本和2个数版本)

问题:一个整数数组中,仅有一个(两个)数出现一次,其他数都出现2次,找出这个数(这两个数)
思路:1.使用异或运算技巧二,分别对所有数异或,最后的数即为所要找的数;
2. 首先利用异或运算得到两个出现一次数的异或结果,然后根据其中某位的1将数组分为两部分(如果异或后的结果某位为1则表示所要找的两个数在该位是不同的,这个划分的依据),然后分别对两个数组求出现一次的数。

// 寻找一个数组中仅出现一次的数,这个数只有一个
    public static int findOnceAppearNumber(int[] nums, int len){
        int result = nums[0];
        for(int i = 1; i < len; i++){
            result ^= nums[i];
        }
        return result;
    }

    // 寻找一个数组中仅出现一次的数,这个数有两个
    public static int[] findOnceAppearNumber2(int[] nums, int len){
        int[] result = new int[2];
        int tmp = nums[0];
        for(int i = 1; i < len; i++){
            tmp ^= nums[i];
        }
        // 得到最后一位的1,并基于此将数组分为两部分
        int bits = tmp & (- tmp);
        List<Integer> arrays1 = new ArrayList<>();
        List<Integer> arrays2 = new ArrayList<>();
        for(int i = 0; i < len; i++){
            if((nums[i] & bits) == 0){
                arrays1.add(nums[i]);
            }else{
                arrays2.add(nums[i]);
            }
        }
        // 分别在两个数组中寻找出现一次的数
        // 此处可以直接调用出现一次的函数
        tmp = arrays1.get(0);
        for(int i = 1; i < arrays1.size(); i++){
            tmp ^= arrays1.get(i);
        }
        result[0] = tmp;
        // result[0] = findOnceAppearNumber(arrays1, array1.size());

        tmp = arrays2.get(0);
        for(int i = 1; i < arrays2.size(); i++){
            tmp ^= arrays2.get(i);
        }
        result[1] = tmp;
        // result[1] = findOnceAppearNumber(arrays2, array2.size());

        return result;
    }

5. 位运算实现加法和减法

问题:使用位运算实现加法和减法
思路:1.对于加法可以手动模拟运算,先进行无进位的加法,然后计算进位,然后循环计算无进位加法结果和进位结果,直到进位为0为止;
2.减法运算,相当于加上一个负数,所以要先求减数的补码,然后调用加法运算;

 // 使用运运算实现加法
    public static int addByBit(int x, int y){
//        // 递归实现
//        if(y == 0){
//            return x;
//        }
//        // 进行不进位加法
//        int sum = x ^ y;
//        // 求进位
//        int carry = (x & y) << 1;
//        // 进位与不进位结果相加
//        return addByBit(sum, carry);
        // 迭代实现
        int sum = x ^ y;
        int carry = (x & y) << 1;
        while(carry != 0) {
            int a = sum;
            int b = carry;
            sum = a ^ b;
            carry = (a & b) << 1;
        }
        return sum;
    }

    // x:减数;y:被减数
    public static int substractByBit(int x, int y){
        // 先求减数的补码(除符号位外取反加1)
        int sub = addByBit(~y, 1);
        // 调用上面加法运算
        return addByBit(x, sub);
    }

6. 输入两数m,n,计算需要改变多少位能使m变成n

问题:给定两个整数,求需要改变多少位使得两个数相同
思路:利用异或运算,只需要改变不同位即可,相当于进行异或运算后求其1的个数

// 给定两个整数,求改变多少位可以使得两个数相同
    public static int changeCount(int n, int m){
        int cnt = 0;
        int tmp = n ^ m;
        while(tmp != 0){
            cnt ++;
            tmp &= (tmp - 1);
        }
        return cnt;
    }

7. 计算[0,n]上各个数的1的个数

问题:输入整数n, 计算0,n中所有数的二进制表示中1的个数,返回一个记录个数的数组
思路:1. 一遍遍历,针对每个数都计算其二进制1的个数;
2. 优化版本,一个数的二进制1个数等于去掉一个数最后一位1所对应的数的个数加1。即:
count[i] = count[i & (i-1)] + 1;

// 计算0,n上各个数的二进制表示中1的个数
    public static int[] findArrayNumberOfOne(int n){
//        //直接遍历的方式
//        int[] result = new int[n+1];
//        for(int i = 0; i <= n; i++){
//            result[i] = numberOfOne1(i);
//        }
        // 优化方式
        int[] result = new int[n+1];
        // 初始化第一个数
        result[0] = 0;
        for(int i = 1; i <= n; i++){
            result[i] = result[i & (i-1)] + 1;
        }
        return result;
    }

8. 位运算解决N皇后问题

问题:N皇后问题
思路:采用位运算

/**
     * 
     * @param row: 当前所在的行数
     * @param col:所有的列
     * @param pie: 左下角位置
     * @param na: 右下角位置,即不允许皇后在一条斜率为1或者-1的斜线上
     */
    public static void nqueen1(int row, int col ,int pie, int na){
        // 递归终止条件
        if(row >= NQUEEN){
            count ++;
            return;
        }
        // 选取一行中可以防止皇后的位置
        int bits = (~(col|pie|na)) & ((1 << NQUEEN)-1);
        while(bits > 0){
            // 在能够放置皇后的位置中选择一个位置,每次从最后的位置选取
            int p = bits & -bits;
            // 往下一层递归
            nqueen1(row+1, (col|p), (pie|p) << 1, (na|p) >>1);
            // 将该行可防止皇后的最后一位去掉,表示该位置已经尝试过
            bits &= bits - 1;
        }
    }

9. bitmap

位图的使用也与位运算紧密相关,可以用于一些大数据处理场景,例如:

  1. 判断一个数是否在一个集合中
    对于一个数仅需要保存其是否存在,只需要两个状态,所以一个位就能满足条件,这样一个int类型的数可以表示32个数的状态,可以把集合中的数通过位运算表示其状态,然后判断一个数是否存在其中。里面涉及到一些简单的移位操作,可以自行搜索。
  2. 布隆过滤器
    判断一个数是否在一个集合中。原理是对于一个数通过若干个哈希函数得到若干个哈希值,将这些哈希值表示在位图中。如果一个数在该集合中,那么其所有哈希值的二进制位都应该是1;如果一个数不在该集合中,原则上其所有哈希值得二进制位不全为1,但是会有误差,因为可能是别的数字存在的缘故导致原本为0的位变为了1,这与位图的大小、哈希函数和哈希函数的个数有关系。感兴趣可以自行搜索。

猜你喜欢

转载自blog.csdn.net/Kaiyang_Shao/article/details/89554132