训练一:位运算

97 阅读4分钟

1. 子集

image.png
class Solution {
    // 二进制枚举
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        int n = nums.length;
        // [0 , 1<<n)
        int maxState = 1 << n;
        for (int i = 0; i < maxState; i++) {
            List<Integer> list = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                // 提取第j位
                int b = (i >> j) & 1;
                if (b == 1) {
                    list.add(nums[j]);
                }
            }
            res.add(list);
        }
        return res;
    }
}

2. 位1的个数

image.png
public class Solution {
    public int hammingWeight(int n) {
        int res = 0;
        while (n != 0) {
            // 去除最后一个1
            n = n & (n - 1);
            res++;
        }
        return res;
    }

    public int hammingWeight(int n) {
        int res = 0;
        for (int i = 0; i < 32; i++) {
            // 提取第i位
            if (((n >> i) & 1) == 1) {
                res++;
            }
        }
        return res;
    }
}

public class Solution {
    // Lowbit理论:
    // 假设x的二进制为  	       010100...10...000
    //        ~x为    	       101011...01...000
    //   -x = ~x + 1           101011...10...000
    // 那么x & -x:	       000000...10...000
    // lowbit = n & (~n+1) = n & (-n)
    // 去除最低位的1: n = n - lowbit
    public int hammingWeight(int n) {
        int res = 0;
        while (n != 0) {
            int lowbit = n & (-n+1);
            n = n - lowbit;
            res++;
        }
        return res;
    }
}

3.形成两个异或相等数组的三元组数目

image.png
class Solution {
    // n ^ n = 0;
    // n ^ 0 = n;
    // S[L,R] = S[0,L-1] ^ S[0,L-1] ^ S[L,R]= S[0,L-1] ^ S[0,R]
    public int countTriplets(int[] arr) {
        int res = 0;
        int n = arr.length;
        int[] nums = new int[n];
        nums[0] = arr[0];
        // 预处理异或前缀和
        for (int i = 1; i < n; i++) {
            nums[i] = nums[i - 1] ^ arr[i];
        }
        for (int i = 0; i < n; i++) {
            for (int k = i + 1; k < n; k++) {
                // S[L] == S[R] , S[L] ^ S[R] = 0
                // S[L,R] = S[L-1]^S[R] == 0
                // 必须写成nums[k],否则i-1越界
                int sum = i == 0 ? nums[k] : nums[i - 1] ^ nums[k];
                if (sum == 0) {
                    // s[i,k]=0,i->k中任意位置取j,s[i,j]=s[j-k]
                    res += (k - i);
                }
            }
        }
        return res;
    }
}

4. 交换数字

image.png
class Solution {
    public int[] swapNumbers(int[] numbers) {
        // 1. 位运算 a ^ b ^ b = a
        // numbers[0] = numbers[0] ^ numbers[1];
        // numbers[1] = numbers[0] ^ numbers[1];
        // numbers[0] = numbers[0] ^ numbers[1];
        // return numbers;
        // 2. 加减法
        numbers[0] = numbers[0] + numbers[1];
        numbers[1] = numbers[0] - numbers[1];
        numbers[0] = numbers[0] - numbers[1];
        return numbers;
    }
}

5. 汉明距离

image.png
class Solution {
    public int hammingDistance(int x, int y) {
        // 方法 1:内置函数
        // return Integer.bitCount(x ^ y);

        // 方法2: 移位
        // int xor = x ^ y;
        // int res = 0;
        // while (xor != 0) {
        //    if (xor % 2 == 1) {
        //        res += 1;
        //    }
        //    xor = xor >> 1;
        // }
        // return res;

        // 方法3: 布赖恩·克尼根(Brian Kernighan)算法
        int xor = x ^ y;
        int res = 0;
        while (xor != 0){
            res ++;
            // 快速去除最后一位1
            xor = xor & (xor - 1);
        }
        return res;
    }
}

6. 整数转换

image.png
class Solution {
    public int convertInteger(int A, int B) {
        // 方法 1:内置函数
        // return Integer.bitCount(A ^ B);

        // 方法2: 移位
        // int xor = A ^ B;
        // int res = 0;
        // while (xor != 0) {
        //    if (xor % 2 == 1) {
        //        res += 1;
        //    }
        //    xor = xor >> 1;
        // }
        // return res;

        // 方法3: 布赖恩·克尼根(Brian Kernighan)算法
        int xor = A ^ B;
        int res = 0;
        while (xor != 0){
            res ++;
            // 快速去除最后一位1
            xor = xor & (xor - 1);
        }
        return res;
    }
}

7. 数字范围按位与

image.png
class Solution {
    // 其实就是求m和n两个二进制字符串的公共前缀
    public int rangeBitwiseAnd(int left, int right) {
        // 布赖恩·克尼根(Brian Kernighan)算法:用于清除二进制串中最右边的1
        // 布赖恩·克尼根(Brian Kernighan)算法的关键是对x和x-1进行按位与运算后,x最右边的1会被mo抹去变成0
        // 基于上述技巧,我们可以用它来计算两个二进制串的公共前缀
        // 其思想是,对于给定范围[m,n],我们可以对n迭代的应用上述技巧,清除最右边的1,直到n<=m,此时非公共前缀部分的1均被消除,因此我们最后返回n即可
        // while (left < right) {
        //     right &= (right - 1);
        // }
        // return right;
        int num = 0;
        while (left < right) {
            num++;
            left = left >> 1;
            right = right >> 1;
        }
        // 前面将left右移了num位,得到了公共前缀
        // 再向左移num位,就得到了前缀不变,后缀都是0的结果
        return left << num;
    }
}

8. 颠倒二进制位

image.png
public class Solution {
    // 取n最后一位: n&1
    public int reverseBits(int n) {
        int res = 0;
        // 二进制反转时,如果剩余位为0,则不需要再反转了
        for (int i = 31; n != 0; i--) {
            res += (n & 1) << i;
            // ">>"  :右移,空缺位用符号位补(该方法会超时报错)
            // ">>>" :无符号右移,空缺位补0
            n = n >>> 1;
        }
        return res;
    }
}

9. 两数相除

image.png

10. 两整数之和

image.png

11. 二进制求和

image.png
class Solution {
    public static String addBinary(String a, String b) {
        StringBuilder ans = new StringBuilder();
        int ca = 0;
        // 易错点: 二进制串右边是低位,字符串左边下标是0,左边是低位
        for (int i = a.length() - 1, j = b.length() - 1; i >= 0 || j >= 0; i--, j--) {
            int sum = ca;
            sum += i >= 0 ? a.charAt(i) - '0' : 0;
            sum += j >= 0 ? b.charAt(j) - '0' : 0;
            // 取最后一位
            ans.append(sum & 1);
            // 取进位
            ca = sum / 2;
        }
        ans.append(ca == 1 ? ca : "");
        return ans.reverse().toString();
    }
}

12. 格雷编码

image.png

13. 只出现一次的数字

image.png
class Solution {
    // 0 ^ n = n;
    // n ^ n = 0;
    public int singleNumber(int[] nums) {
        int res = 0;
        for (int num : nums) {
            res = res ^ num;
        }
        return res;
    }
}

14. 数字的补数

image.png
class Solution {
    public int findComplement(int num) {
        int res = 0;
        int i=0;
        while(num != 0){
            if(num % 2 ==0){
                res = res | 1<<i;
            }
            i++;
            num /=2;
        }
        return res;
    }
}

class Solution {
    public int findComplement(int num) {
        int res = 0;
        for (int i = 0; i < 31; i++) {
            // 注意:最容易出错的地方,因为高位全是0,i不可能遍历完
            if((1<<i) > num){
                break;
            }
            // 提取第i位
            int b = (num>>i) & 1;
            if (b==0){
                res |= 1<<i;
            }
        }
        return res;
    }
}

15. 只出现一次的数字

image.png
class Solution {
    // 0 ^ n = n;
    // n ^ n = 0;
    public int singleNumber(int[] nums) {
        int eor = 0;
        for (int num : nums) {
            eor = eor ^ num;
        }
        return eor;
    }
}

16. 只出现一次的数字 II

image.png
class Solution {
    public int singleNumber(int[] nums) {
        int res = 0;
        // 依次确定每一个二进制位
        for(int i = 0 ; i< 32 ; i++){
            int sum = 0;
            for(int num: nums){
                sum += (num >> i) & 1;
            }
            // 答案的第i个二进制位就是数组中所有元素的第i个二进制位之和除以3的余数
            if(sum % 3 !=0){
                res |= 1<<i;
            }
        }
        return res;
    }
}

17. 只出现一次的数字 III

image.png
class Solution {
    public int[] singleNumber(int[] nums) {
        int eor = 0;
        for (int num : nums) {
            eor = eor ^ num;
        }
        // 找到eor最右边的1
        int idx = 0;
        for (int i = 0; i < 32; i++) {
            if ((eor >> i & 1) == 1) {
                idx = i;
                break;
            }
        }
        // a和b的第idx位一定是一个0一个1
        int aEor = 0;
        for (int num : nums) {
            // 找出第idx位为1的元素进行异或,就能得到a
            if ((num >> idx & 1) == 1) {
                aEor = aEor ^ num;
            }
        }
        int bEor = eor ^ aEor;
        return new int[]{aEor, bEor};
    }
}

18. 2的幂

image.png
class Solution {
    public boolean isPowerOfTwo(int n) {
        if(n<=0){
            return false;
        }
        if((n & (n-1))==0){
            return true;
        }
        return false;
    }
}

19. 格雷编码

image.png
class Solution {
    // n转格雷码:   g(n) = n ^ (n>>1)  解释: 格雷码的特点是第i位为1,则第i+1位为0;第i位为0,则第i+1位为1
    // 格雷码转数:  int n =0 ;  while( g != 0) { n ^= g ; g = g >> 1 );
    public List<Integer> grayCode(int n) {
        List<Integer> list = new ArrayList<>();
        int i = 0;
        while (i < 1 << n) {
            list.add(i ^ (i >> 1));
            i++;
        }
        return list;
    }
}