常用的位操作
学习 labuladong 的算法小抄,原文地址https://labuladong.gitee.io/algo/4/32/114/
一、几个有趣的位操作
- 利用或操作
|
和空格' '
将英文字符转换为小写
('a' | ' ') = 'a'
('A' | ' ') = 'a'
- 利用与操作
&
和下划线'_'
将英文字符转换为大写
('a' & '_') = 'A'
('A' & '_') = 'A'
- 利用异或操作
^
和空格' '
进行英文字符大小写互换
('a' ^ ' ') = 'A'
('A' ^ ' ') = 'a'
原理
在ASCII码表中,空格的ASCII码为32,二进制表示为00100000;下划线的ASCII码为95,二进制表示为01011111.大写字母A~Z的ASCII码为65~90,二进制表示为01000001~ 01011010。小写字母a~z的ASCII码为97~122,二进制为01100001~ 01111010。
- 判断两个数是否异号
int x = -1, y = 2;
boolean f = ((x ^ y) < 0); //true
int x = 3, y = 2;
boolean f = ((x ^ y) < 0); //false
原理
在计算机系统中,数值一律用补码表示和存储。含符号位和数值位,符号位:0表示“正”; 1表示“负”。
正数的补码 = 原码
负数的补码 = 负数的原码取反(符号位保持不变)+ 1
原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。
所以,正数的补码最高位是0,负数的补码最高位是1。而1^0 = 1,0^0=1, 1^1=0,可以看到只有两异号数进行异或操作后其最高位为1,同号数最高位则为0。
二、n & (n-1)
的运用
n & (n-1)
这个操作是算法中常见的,作用是消除数字 n 的二进制表示中的最后一个 1
在二进制表示中,如果第k值为1,则表示2^(k-1),所以对于数字n,如果要表示n-1,其最后一位1应置为0,低位全部置为1。
例题
- 计算汉明权重(Hamming Weight)
因为 n & (n - 1) 可以消除最后一个 1,所以可以用一个循环不停地消除 1 同时计数,直到 n 变成 0 为止。
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
n = n & (n - 1);
res++;
}
return res;
}
}
- 判断一个数是不是 2 的指数
一眼看出解法,判断是否只有一位1即可,注意0和负数即可
class Solution {
public boolean isPowerOfTwo(int n) {
if(n <= 0) return false;
n &= n-1;
return n == 0;
}
}
三、a ^ a = 0
的运用
异或运算的性质是需要我们牢记的:
一个数和它本身做异或运算结果为 0,即 a ^ a = 0;一个数和 0 做异或运算的结果为它本身,即 a ^ 0 = a。
例题
- 查找只出现一次的元素
对于这道题目,我们只要把所有数字进行异或,成对儿的数字就会变成 0,落单的数字和 0 做异或还是它本身,所以最后异或的结果就是只出现一次的元素。
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for(int n : nums) {
res ^= n;
}
return res;
}
}
再回顾一下异或运算的性质:一个数和它本身做异或运算结果为 0,一个数和 0 做异或运算还是它本身。
而且异或运算满足交换律和结合律,也就是说:
2 ^ 3 ^ 2 = 3 ^ (2 ^ 2) = 3 ^ 0 = 3
而这道题就可以通过这些性质巧妙算出缺失的那个元素,比如说 nums = [0,3,1,4]
:
了容易理解,我们假设先把索引补一位,然后让每个元素和自己相等的索引相对应:
这样做了之后,就可以发现除了缺失元素之外,所有的索引和元素都组成一对儿了,现在如果把这个落单的索引 2 找出来,也就找到了缺失的那个元素。
如何找这个落单的数字呢,只要把所有的元素和索引做异或运算,成对儿的数字都会消为 0,只有这个落单的元素会剩下,也就达到了我们的目的:
int missingNumber(int[] nums) {
int n = nums.length;
int res = 0;
// 先和新补的索引异或一下
res ^= n;
// 和其他的元素、索引做异或
for (int i = 0; i < n; i++)
res ^= i ^ nums[i];
return res;
}