位操作

技巧

用的多的

  1. 如果n只有1个1,那么: n&(n-1)的结果就是0
  2. x & (-x) 是保留位中最右边 1
  3. Integer.bitCount(n)

基本原理

0s 表示一串 0,1s 表示一串 1。

x ^ 0s = x      x & 0s = 0      x | 0s = x
x ^ 1s = ~x     x & 1s = x      x | 1s = 1s
x ^ x = 0       x & x = x       x | x = x
  • 利用 x ^ 1s = ~x 的特点,可以将位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。
  • 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask:00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。
  • 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。

位与运算技巧:

  • n&(n-1) 去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110100,减去 1 得到 10110011,这两个数相与得到 10110000。
  • n&(-n) 得到 n 的位级表示中最低的那一位。-n 得到 n 的反码加 1,对于二进制表示 10110100,-n 得到 01001100,相与得到 00000100。
  • n-n&(~n+1) 去除 n 的位级表示中最高的那一位。

移位运算:

  • >> n 为算术右移,相当于除以 2n
  • >>> 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);     // 转换为二进制表示的字符串

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

  1. Hamming Distance (Easy)

Leetcode / 力扣

https://www.bilibili.com/video/BV1pJ411M7mb/?spm_id_from=333.788.videocard.3

Input: x = 1, y = 4

Output: 2

Explanation:
1   (0 0 0 1)
4   (0 1 0 0)
       ↑   ↑

The above arrows point to positions where the corresponding bits are different.

对两个数进行异或操作,位级表示不同的那一位为 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;
}

使用 z&(z-1) 去除 z 位级表示最低的那一位。

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

可以使用 Integer.bitcount() 来统计 1 个的个数。

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

2. 数组中唯一一个不重复的元素

136. Single Number (Easy)

Leetcode / 力扣

Input: [4,1,2,1,2]
Output: 4

两个相同的数异或的结果为 0,对所有数进行异或操作,最后的结果就是单独出现的那个数。

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

3. 找出数组中缺失的那个数

268. Missing Number (Easy)

Leetcode / 力扣

Input: [3,0,1]
Output: 2

题目描述:数组元素在 0-n 之间,但是有一个数是缺失的,要求找到这个缺失的数。

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;
}

4. 数组中不重复的两个元素

260. Single Number III (Medium)

Leetcode / 力扣

两个不相等的元素在位级表示上必定会有一位存在不同。

将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。

diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。

public int[] singleNumber(int[] nums) {
    int diff = 0;
    for (int num : nums) diff ^= num;
    diff &= -diff;  // 得到最右一位
    int[] ret = new int[2];
    for (int num : nums) {
        //取到所有最右一位是0的数异或,最后得到只有最后一位为一的x
        if ((num & diff) == 0) ret[0] ^= num;
        //取到
        else ret[1] ^= num;
    }
    return ret;
}

//https://leetcode-cn.com/problems/single-number-iii/solution/zhi-chu-xian-yi-ci-de-shu-zi-iii-by-leetcode/




5. 翻转一个数的比特位

190. Reverse Bits (Easy)

Leetcode / 力扣

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


public int reverseBits(int n) {
    int res = 0;
    int count = 0;
    while (count < 32) {
        res <<= 1;  //res 左移一位空出位置
        res |= (n & 1); //得到的最低位加过来
        n >>= 1;//原数字右移一位去掉已经处理过的最低位
        count++;
    }
    return res;
}
作者:windliang
链接:https://leetcode-cn.com/problems/reverse-bits/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-4-9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



//每次移动32
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++){
            //每次都处理二进制的最低位, 这样方便进行&操作
            int cur = n&1;
            //直接把二进制的最低位安排到最终位置上, 不过怎么安排左移和右移???? 我是个蠢蛋, 只需要左移, 不需要右移!! 
            res = res + (cur<<(31-i));
            //update
            n = n>>1;
        }
        return res;
    }
}

作者:littlehaes
链接:https://leetcode-cn.com/problems/reverse-bits/solution/javaji-bai-100-by-littlehaes/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

如果该函数需要被调用很多次,可以将 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;
}

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

程序员代码面试指南 :P317

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

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

231. Power of Two (Easy)

Leetcode / 力扣

二进制表示只有一个 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;
}

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

342. Power of Four (Easy)

Leetcode / 力扣

这种数在二进制表示中有且只有一个奇数位为 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*");
}

9. 判断一个数的位级表示是否不会出现连续的 0 和 1(内含判断是否全是1)

693. Binary Number with Alternating Bits (Easy)

Leetcode / 力扣

Input: 10
Output: True
Explanation:
The binary representation of 10 is: 1010.

Input: 11
Output: False
Explanation:
The binary representation of 11 is: 1011.

对于 1010 这种位级表示的数,把它向右移动 1 位得到 101,这两个数每个位都不同,因此异或得到的结果为 1111。

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

10. 求一个数的补码(每个位求非)

476. Number Complement (Easy)

Leetcode / 力扣

// 在于没有前导0位,即首位肯定是1,所以可以用右移一位的方式,与原数异或

Input: 5
Output: 2
Explanation: The binary representation of 5 is 101 (no leading zero bits), and its complement is 010. So you need to output 2.

题目描述:不考虑二进制表示中的首 0 部分。

对于 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;
}

可以利用 Java 的 Integer.highestOneBit() 方法来获得含有首 1 的数。

public int findComplement(int num) {
    if (num == 0) return 1;
    int mask = Integer.highestOneBit(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);
}

11. 实现整数的加法

371. Sum of Two Integers (Easy)

Leetcode / 力扣

a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。

递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。

https://www.bilibili.com/video/BV1pJ411M7mb/?spm_id_from=333.788.videocard.3

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

12. 字符串数组最大乘积

//很巧妙,使用一个int来标记两个单词是否有交际

318. Maximum Product of Word Lengths (Medium)

Leetcode / 力扣

Given ["abcw", "baz", "foo", "bar", "xtfn", "abcdef"]
Return 16
The two words can be "abcw", "xtfn".

题目描述:字符串数组的字符串只含有小写字符。求解字符串数组中两个字符串长度的最大乘积,要求这两个字符串不能含有相同字符。

本题主要问题是判断两个字符串是否含相同字符,由于字符串只含有小写字符,总共 26 位,因此可以用一个 32 位的整数来存储每个字符是否出现过。

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;
}

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

338. Counting Bits (Medium)

Leetcode / 力扣

对于数字 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;
}

vector<int> countBits(int num) {
        vector<int> result(num+1);
        result[0] = 0;
        for(int i = 1; i <= num; i++)
        {
            if(i % 2 == 1) //奇数比前一个多一个1
            {
                result[i] = result[i-1] + 1;
            }
            else  //偶数与除以2后的相同
            {
                result[i] = result[i/2];
            }
        }
        
        return result;
    }

作者:duadua
链接:https://leetcode-cn.com/problems/counting-bits/solution/hen-qing-xi-de-si-lu-by-duadua/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

14.连续数字的按位与

public int rangeBitwiseAnd(int m, int n) {
    int zeros = 0;
    while (n > m) {
        zeros++;
        m >>= 1;
        n >>= 1;
    }
    //将 0 的个数空出来
    return m << zeros;
}


二进制中1的个数

  • 方法1

O(32)的做法: 将各位为1的数加起来

class Solution {
    public boolean isPowerOfTwo(int n) {
        //二进制中1的个数为1,且这个数还不是1
        int res = 0;
        while(n != 0){
            if (res > 1) return false;
            if((n & 1 )== 1){
                res++;
            }
            n >>= 1;
        }
        return res==1;
    }
}
  • 方法2

进阶一步:将所有位的1置位0.除了最右边的那个1,看结果是否与原来的数相等,相等就是2的次幂。
一个巧妙的方法求最右面的一位

x & (-x) 可以获取到二进制中最右边的 1,且其它位设置为 0。
在补码表示法中,-x = \lnot x + 1−x=¬x+1。换句话说,要计算 -x−x,则要将 xx 所有位取反再加 1。

在二进制表示中,\lnot x + 1¬x+1 表示将该 1 移动到 \lnot x¬x 中最右边的 0 的位置上,并将所有较低位的位设置为
0。而 \lnot x¬x 最右边的 0 的位置对应于 xx 最右边的 1 的位置。

总而言之,-x = \lnot x + 1−x=¬x+1,此操作将 xx 所有位取反,但是最右边的 1 除外。

作者:LeetCode
链接:https://leetcode-cn.com/problems/power-of-two/solution/2de-mi-by-leetcode/
来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

class Solution {
  public boolean isPowerOfTwo(int n) {
    if (n == 0) return false;
    long x = (long) n;
    return (x & (-x)) == x;
  }
}
  • 方法3

如果n只有1个1,那么: n&(n-1)的结果就是0

十进制中的位操作

233. 数字 1 的个数

总体思想就是分类,先求所有数中个位是 1 的个数,再求十位是 1 的个数,再求百位是 1 的个数…

假设 n = xyzdabc,此时我们求千位是 1 的个数,也就是 d 所在的位置。

那么此时有三种情况,

d == 0,那么千位上 1 的个数就是 xyz * 1000
d == 1,那么千位上 1 的个数就是 xyz * 1000 + abc
d > 1,那么千位上 1 的个数就是 xyz * 1000 + 1000

作者:windliang
链接:https://leetcode-cn.com/problems/number-of-digit-one/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-50/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

class Solution {
    public int countDigitOne(int n) {
        int res = 0;
        //不是逐个数字计算,而是计算所有数字每位上的数的和
        for (int k = 1; k <= n; k *= 10) {
            int a = n/k;
            res += a / 10 * k;
            //判断此位数的值
            if(a % 10 == 1) res +=  n % k + 1;
            if(a % 10 > 1) res += k;
            //如果所关注的位的前面都是0了,直接退出
            if(a /10 == 0) break;
        }
        return res;
    }
}

260. 只出现一次的数字 III

class Solution {
    public int[] singleNumber(int[] nums) {
        //只出现一次的数字
        int mask = 0;
        for (int i : nums) mask ^= i;
        int diff = mask & (- mask);
        //再讲所有在diff为1上的位上也为1的数异或,得到的肯定是其中的一个数
        int x = 0;
        for (int i : nums) {
            if((i & diff) == 0)  x ^= i; 
        }
        return new int[]{x, mask^x};
    }
}

没有出现的那个数
268. 缺失数字

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

作者:LeetCode
链接:https://leetcode-cn.com/problems/missing-number/solution/que-shi-shu-zi-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值