算法【Java】—— 位运算
位运算总结
位运算的运算符:按位与(&),按位或(|),按位异或(^),按位取反(~),还有移位操作符 <<,>>
确定一个数 n 的第 x 位是0 还是 1
(n >> x) & 1
等于 1 / 0 来进行判断
将 n 的第 x 位修改为 1
n |= (1 << x)
将 n 的第 x 位修改为0
n &= ~(1 << x)
提取 n 二进制最右侧的 1
n & -n
删除 n 二进制最右侧的 1
n & (n - 1)
异或运算的规律
a ^ a = 0
a ^ 0 = a
a ^ b ^ c = a ^ c ^ b (满足交换律)
位运算的优先级可以通过我们手动添加括号来解决,这样就可以减少我们的记忆负担了。
位图思想:和哈希表类似,只不过位图采用的是比特位标记的方式来记录数字。
实战演练
只出现一次的数字 Ⅰ
https://leetcode.cn/problems/single-number/
由于除了一个元素只出现一次,其他元素均出现了两次,所以我们可以将所有的数字进行异或运算,最后得到的结果就是只出现一次的元素。
class Solution {
public int singleNumber(int[] nums) {
int ans = 0;
for(int x : nums) {
ans ^= x;
}
return ans;
}
}
只出现一次的数字 Ⅲ
https://leetcode.cn/problems/single-number-iii/
假设最后的答案分别是 a ,b
按照上一道题目的思路,我们将所有的数字进行异或得到的结果为 a ^ b
现在我们需要将他们两个分开来,如何分开?首先这两个数字一定是不相同的,那么在某一个比特位上一定不同,我们找到这个不同的比特位,然后根据这个不同的比特位最为划分标准,将数组所有的元素分成两组,这两组再分别进行异或操作,最后就会得到 a 与 b 这两个数字。
如何找到不同的比特位?因为异或的无进位相加(相同的异或结果为0,相异的异或结果为1),所有只要能找到比特位为1,这个位置就可以作为后续的判断标准。这里我们就可以使用n & -n
获取n 最后一位的 1
class Solution {
public int[] singleNumber(int[] nums) {
int sum = 0;
for(int x : nums) {
sum ^= x;
}
sum &= -sum;
int ret1 = 0, ret2 = 0;
for(int x : nums) {
if((x & sum) == 0) {
ret1 ^= x;
} else {
ret2 ^= x;
}
}
return new int[]{ret1,ret2};
}
}
位 1 的个数
https://leetcode.cn/problems/number-of-1-bits/
直接使用 n & (n-1)
即可
class Solution {
public int hammingWeight(int n) {
int count = 0;
while(n != 0) {
n &= (n-1);
count++;
}
return count;
}
}
比特位计数
https://leetcode.cn/problems/counting-bits/
class Solution {
public int[] countBits(int n) {
int[] ans = new int[n+1];
for(int i = 0; i <= n; i++) {
int count = 0;
int tmp = i;
while(tmp != 0) {
tmp &= (tmp - 1);
count++;
}
ans[i] = count;
}
return ans;
}
}
汉明距离
https://leetcode.cn/problems/hamming-distance/
计算不同的二进制位数,首先进行异或运算,然后计算这个结果有多少个 1 即可
class Solution {
public int hammingDistance(int x, int y) {
int sum = x ^ y;
int count = 0;
while(sum != 0) {
sum &= (sum-1);
count++;
}
return count;
}
}
判定字符是否唯一
https://leetcode.cn/problems/is-unique-lcci/description/
这里我们首先会想到哈希表,但是如果不适用额外的数据结构,我们可以考虑位图,用一个变量上的比特位来记录数据,正好字符串都是小写字母,也就是26种字母,使用 26 个比特位就可以保存数据的状态。
class Solution {
public boolean isUnique(String astr) {
if(astr.length() > 26) { //鹊巢原理
return false;
}
int bitSet = 0;//位图
char[] arr = astr.toCharArray();
for(char x : arr) {
if((1 << (x-'a') & bitSet) != 0) {
return false;
}
bitSet |= (1 << (x-'a'));
}
return true;
}
}
丢失的数字
https://leetcode.cn/problems/missing-number/description/
这道题可以使用等差数列求和公式来做
当然这里是位运算专题,我们可以使用为运算来做,首先我们可以将 0 ~ n 之间所有的数字进行异或运算,然后将数组里的所有数字和上一次异或的结果一起异或,这样就可以找到丢失的数字了。
class Solution {
public int missingNumber(int[] nums) {
int sum = 0;
for(int i = 0; i <= nums.length; i++) {
sum ^= i;
}
for(int x : nums) {
sum ^= x;
}
return sum;
}
}
两整数之和
https://leetcode.cn/problems/sum-of-two-integers/description/
我们知道异或运算可以得到无进位相加的结果,我们知道当两个比特位都为 1 进行相加的时候是需要进位的,所以我们可以通过两个操作数按位与再左移一位就可以知道哪一些比特位需要进行进位,再次重复上述操作,知道最后没有需要进位,就可以得到两个数字相加的结果。
class Solution {
public int getSum(int a, int b) {
while(b != 0) {
int sum = a ^ b;
b = (b & a) << 1;
a = sum;
}
return a;
}
}
只出现一次的数字 Ⅱ
https://leetcode.cn/problems/single-number-ii/description/
只有一个元素出现一次,其他元素均出现三次,那么我们可以进行比特位计数,计算每一位上所有的数字的 1 的个数,最后将结果 %3 ,就会得到只出现一次的元素的那一位比特位上是 1 还是 0.
一个整型数据由有32 个比特位,所以进行三十二次循环。
class Solution {
public int singleNumber(int[] nums) {
int ans = 0;
for(int i = 0; i < 32; i++) {
int count = 0;
for(int x : nums) {
if(((1 << i) & x) != 0) {
count++;
}
}
if(count % 3 == 1) {
ans |= (1 << i);
}
}
return ans;
}
}
消失的两个数字
https://leetcode.cn/problems/missing-two-lcci/
这道题我们先将 1 ~ n 和数组所有的数字异或就可以得到消失的两个数字的异或结果(这里记为 a ^ b),现在就是分离他们了,很简单,和上面一样,找到最后的比特位为 1,以这个为标准开始划分,将 1 ~ n 和数组所有的元素都进行划分就可以得到最终的两个数字了。
class Solution {
public int[] missingTwo(int[] nums) {
int n = nums.length;
int sum = 0;
for(int i = 1; i <= n + 2; i++) {
sum ^= i;
}
for(int x : nums) {
sum ^= x;
}
sum &= -sum;
int ret1 = 0, ret2 = 0;
for(int i = 1; i <= n + 2; i++) {
if((i & sum) != 0) {
ret1 ^= i;
} else {
ret2 ^= i;
}
}
for(int x : nums) {
if((x & sum) != 0) {
ret1 ^= x;
} else {
ret2 ^= x;
}
}
return new int[]{ret1,ret2};
}
}