三道题搞懂位运算
1、位运算优先级
2、题目
(1)LeetCode1386. 安排电影院座位
class Solution {
// 采用补集的思想 先计算最多有多少座位(无占座行数 * 2) 再减去所有 有占座的座位*2 再 加上能坐的座位
public int maxNumberOfFamilies(int n, int[][] reservedSeats) {
//用哈希表记录被占的座位行列
Map<Integer, Integer> hash = new HashMap<>();
for(int[] seat: reservedSeats){
//x 代表行 y代表列
int x = seat[0], y = seat[1];
if(y != 1 && y != 10)
// 将2~9映射到0~7 用7位的二进制数表示这个位置有没有被占用,如果是1 则被占用
// 若没有就是0 若有就是原值 | 1 << y -2
hash.put(x, hash.getOrDefault(x, 0) | 1 << y -2);
}
int left = 0b11110000;
int right = 0b00001111;
int mid = 0b00111100;
int res = n * 2 - hash.size() * 2;
//entrySet()返回映射所包含的映射关系的Set集合(一个关系就是一个键-值对),就是把(key-value)作为一个整体一对一对地存放到Set集合当中。
for(Map.Entry<Integer, Integer> entry: hash.entrySet()){
int v = entry.getValue();
if((v & left) == 0 || (v & right) == 0 || (v & mid) == 0)
res ++;
}
return res;
}
}
(2)LeetCode1371. 每个元音包含偶数次的最长子字符串
思路:
aeiou一共五个字母,用二进制来表示每个字母出现的奇偶情况。用1来表示奇数个,0表示偶数个。所以一共有2^5 = 32 种情况 。可以开一个数组或哈希表记录每一种状态。
然后遍历字符串s,枚举每个字符串中的字母,利用元音字母使得每个字母对应一种状态。如果有重复的状态,用当前的下标减去第一次出现这个状态的下标取MAX即可,否则将这个状态添加到哈希表或数组中。需要注意的是,**hash[0]的值应该为-1。**因为hash[0] 表示所有的元音字母数都是偶数,上一次出现这个状态应该是第一个字母的前一个字母,所以下标为-1。
//状态压缩 + 位运算
class Solution {
/*
每次记录第一次出现每种状态的下标
*/
public int findTheLongestSubstring(String s) {
//存储第一次出现这个状态的最小的位置(下标)
int[] hash = new int[32];
Arrays.fill(hash, -2);
hash[0] = -1;
String cs = "aeiou";
int res = 0, state = 0;
for(int i = 0; i < s.length(); i ++ ){
int k = cs.indexOf(s.charAt(i));
if(k != -1) state ^= 1 << k;
if(hash[state] != -2) res = Math.max(res, i - hash[state]);
else hash[state] = i;
}
return res;
}
}
(3)LeetCode2401. 最长优雅子数组
思路:
用异或运算符和变量 state记录所有数字在位数上的 1 的出现情况,一旦出现冲突(也就是新近数字和原来的 state在同一位上出现 1)就不停地右移窗口左边界,直到删掉前方所有和新数字 nums[i] 在位数上有冲突的数字为止
小技巧:
x & y == 0 时 x | y ^ y = x
// 滑动窗口+位运算
class Solution {
public int longestNiceSubarray(int[] nums) {
int res = 0;
int n = nums.length;
for(int l = 0, r = 0, state = 0; r < n; r ++ ){
while ((state & nums[r] )> 0)
state ^= nums[l ++];
state |= nums[r];
res = Math.max(res, r - l + 1);
}
return res;
}
}