LeetCode-数据结构-位操作

基本原理

逻辑取反和按位取反的区分
异或操作可以进行位值翻转以及数据奇数个的判断。
按位与可以进行掩码操作,保留位值为一的部分的值。
按位或可以进行设值操作,一对应的部分均设定为一。
n&(n-1) 去除 n 的位级表示中最低的那一位 1。
n&(-n) 得到 n 的位级表示中最低的那一位 1。 -n=~n+1。
n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1) 效果一样。
/>> n 为算术右移,相当于除以 2n,例如 -7 >> 2 = -2。
/>>>> n 为无符号右移,左边会补上 0。
/<< n 为算术左移,相当于乘以 2n。

mask计算

要获取 111111111,将 0 取反即可,~0。要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可,1<<(i-1) 。例如 1<<4 得到只有第 5 位为 1 的 mask :00010000。要得到 1 到 i 位为 1 的 mask,(1<<i)-1 即可,例如将 (1<<4)-1 = 00010000-1 = 00001111。要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 ~((1<<i)-1)。

JAVA中的位操作

static int Integer.bitCount(); // 统计 1 的数量
static int Integer.highestOneBit(); // 获得最高位
static String toBinaryString(int i); // 转换为二进制表示的字符串

461 统计两数的二进制表示有多少位不同

调用函数法

public int hammingDistance(int x, int y) {
    return Integer.bitCount(x ^ y);
}

统计法
单个1表示只有最后一位为1。

public int hammingDistance(int x, int y) {
    int z = x ^ y;
    int cnt = 0;
    while(z != 0) {
        if ((z & 1) == 1) cnt++;
        z = z >> 1;
    }
    return cnt;
}

操作符法

public int hammingDistance(int x, int y) {
    int z = x ^ y;
    int cnt = 0;
    while (z != 0) {
        z &= (z - 1);
        cnt++;
    }
    return cnt;
}

136 数组中唯一不重复元素

常见异或操作应用

public int singleNumber(int[] nums) {
    int ret = 0;
    for (int n : nums) ret = ret ^ n;
    return ret;
}

268 找出数组中缺失的那个数

利用数据的有序性,数据具有下标索引值,可将数据与下标进行异或,判断未出现的数字。

public int missingNumber(int[] nums) {
    int ret = 0;
    for (int i = 0; i < nums.length; i++) {
        ret = ret ^ i ^ nums[i];
    }
    return ret ^ nums.length;
}

注意少包含一个数字就会需要多验证一个长度的数字。

260 数组中不重复的两个元素

两个不相等的元素在位级表示上必定会有一位存在不同。
将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。
diff &= -diff 得到出diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。
之后继续与该位进行异或,数据可以分为两类来进行。

public int[] singleNumber(int[] nums) {
    int diff = 0;
    for (int num : nums) diff ^= num;
    diff &= 4-diff;  // 得到最右一位
    int[] ret = new int[2];
    for (int num : nums) {
        if ((num & diff) == 0) ret[0] ^= num;
        else ret[1] ^= num;
    }
    return ret;
}

190 翻转一个数的比特位

这里的颠倒翻转是指左右的颠倒翻转

public int reverseBits(int n) {
    int ret = 0;
    for (int i = 0; i < 32; i++) {
        ret <<= 1;
        ret |= (n & 1);
        n >>>= 1;
    }
    return ret;
}

如果该函数需要被调用很多次,可以将 int 拆成 4 个 byte,然后缓存 byte 对应的比特位翻转,最后再拼接起来。

private static Map<Byte, Integer> cache = new HashMap<>();

public int reverseBits(int n) {
    int ret = 0;
    for (int i = 0; i < 4; i++) {
        ret <<= 8;
        ret |= reverseByte((byte) (n & 0b11111111));
        n >>= 8;
    }
    return ret;
}

private int reverseByte(byte b) {
    if (cache.containsKey(b)) return cache.get(b);
    int ret = 0;
    byte t = b;
    for (int i = 0; i < 8; i++) {
        ret <<= 1;
        ret |= t & 1;
        t >>= 1;
    }
    cache.put(b, ret);
    return ret;
}

不用额外变量交换两个整数

a = a ^ b;
b = a ^ b;
a = a ^ b;

231 判断一个数是不是 2 的 n 次方

二进制表示只有一个 1 存在。调用函数

public boolean isPowerOfTwo(int n) {
    return n > 0 && Integer.bitCount(n) == 1;
}

利用 1000 & 0111 == 0 ,本质解法。

   public boolean isPowerOfTwo(int n) {         
    return n>0 && (n &(n-1))==0;    }

342 判断一个数是不是 4 的 n 次方

这种数在二进制表示中有且只有一个奇数位为 1,例如 16(10000)

public boolean isPowerOfFour(int num) {
    return num > 0 && (num & (num - 1)) == 0 && (num & 0b01010101010101010101010101010101) != 0;
}

使用正则表达匹配

public boolean isPowerOfFour(int num) {
    return Integer.toString(num, 4).matches("10*");
}

693 判断一个数的位级表示是否不会出现连续的 0 和 1

位移异或

public boolean hasAlternatingBits(int n) {
    int a = (n ^ (n >> 1));
    return (a & (a + 1)) == 0;
}

476 求一个数的补码

对于 00000101,要求补码可以将它与 00000111 进行异或操作。那么问题就转换为求掩码 00000111。不考虑符号位首零情况。

public int findComplement(int num) {
    if (num == 0) return 1;//避免无线循环情况的发生
    int mask = 1 << 30;
    while ((num & mask) == 0) mask >>= 1;
    mask = (mask << 1) - 1;
    return num ^ mask;
}

利用函数来获取含有首一的情况

public int findComplement(int num) {
    if (num == 0) return 1;
    int mask=Integer.bitCount(num);
    mask = (mask << 1) - 1;    
    return num ^ mask;
}

对于 10000000 这样的数要扩展成 11111111,可以利用以下方法:
针对该问题的特殊解法

mask |= mask >> 1    11000000
mask |= mask >> 2    11110000
mask |= mask >> 4    11111111
public int findComplement(int num) {
    int mask = num;
    mask |= mask >> 1;
    mask |= mask >> 2;
    mask |= mask >> 4;
    mask |= mask >> 8;
    mask |= mask >> 16;
    return (mask ^ num);
}

371 实现整数的加法

a ^ b 表示没有考虑进位的情况下两数的和
(a & b) << 1 就是进位
递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。

public int getSum(int a, int b) {
    return b == 0 ? a : getSum((a ^ b), (a & b) << 1);
}

318 字符串数组最大乘积

使用位操作方法

public int maxProduct(String[] words) {
    int n = words.length;
    int[] val = new int[n];
    for (int i = 0; i < n; i++) {
        for (char c : words[i].toCharArray()) {
            val[i] |= 1 << (c - 'a');
        }
    }
    int ret = 0;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if ((val[i] & val[j]) == 0) {
                ret = Math.max(ret, words[i].length() * words[j].length());
            }
        }
    }
    return ret;
}

338 统计从 0 ~ n 每个数的二进制表示中 1 的个数

对于数字 6(110),它可以看成是 4(100) 再加一个 2(10),因此 dp[i] = dp[i&(i-1)] + 1;
注意计算中的使用规律

public int[] countBits(int num) {
    int[] ret = new int[num + 1];
    for(int i = 1; i <= num; i++){
        ret[i] = ret[i&(i-1)] + 1;
    }
    return ret;
}

部分解答思路参考自:https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E4%BD%8D%E8%BF%90%E7%AE%97.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值