先说核心,怎么使用位运算来检查重复。
一个int类型的数有32位,用二进制来表示就是
00000000000000000000000000000000
至
11111111111111111111111111111111
是不是很像一个32位的boolean类型的数组
接下来我们在讲一下位运算
|运算:0000|0101 = 0101
&运算:0011&1001 = 0001
^运算:0011^1001 = 1010
~运算:~1100 = 0011
我们怎么使用位运算来表示boolean数组.
先设置int k = 0等同于建了一个32位的boolean数组;
那就是
00000000000000000000000000000000
现在第2位设为true
00000000000000000000000000000100
对于k来说,就是k=k|(1<<2);
现在判断第5位是否为true
00000000000000000000000000000100
与
00000000000000000000000000100000
进行&运算
那就是:if((k&(1<<5))>0);
理解了上面这些,就可以实战了
这时候给你一串字符串,全部由小写字母组成,现在问你字符串中保存了哪几个不重复的字母。
去重嘛,第一反应是使用set,每个字母都保存到set中,到时候set中有的字母就是答案了。
然后有些人会反应过来使用boolean[]的数组,都是小写字母,所以字母最多26中,建个26长度的boolean[],出现’a’,将第0位置成true,出现’b’,将第1位置成true,等统计完成后,在回数组中查找,为true的位数就是存在。
使用位运算,其实本质是和boolean[]类型一样的。先上代码吧。
public String getCharNum(String s) {
char[] chars = s.toCharArray();
int k = 0;
for (char c : chars) {
//或运算
k = k | (1 << (c - 'a'));
}
String ans = "";
for (int i = 0; i < 26; i++) {
//且运算
if ((k & (1 << i)) != 0) {
ans += (char) (i + 'a');
}
}
return ans;
}
所以在boolean[]数组长度较短时,是可以保存到一个int或者long中去的。
来现在回到面试题 01.01. 判定字符是否唯一,当然,这道题按照要求是不能使用位运算的,毕竟建一个long也是算使用额外的数据结构了。
我们暂时不考虑这个,在不建立数组或者集合的情况下,实现这道题。
这里的特别之处是,char类型是128位,一个long类型64位,不够啊,那就用两个long
public boolean isUnique(String astr) {
long left = 0;
long right = 0;
for (int i = 0; i < astr.length(); i++) {
if (astr.charAt(i) >= 64) {
if ((left & (1 << (astr.charAt(i) - 64))) != 0) {
return false;
}
left += (1 << (astr.charAt(i) - 64));//这一步和left |= (1 << (astr.charAt(i) - 64));本质上是一样的
} else {
if ((right & (1 << astr.charAt(i))) != 0) {
return false;
}
right += (1 << astr.charAt(i));//这一步和left |= (1 << astr.charAt(i));本质上是一样的
}
}
return true;
}
基本上可以说,能用较短boolean数组实现的,都可以使用位运算,但是可以使用位运算的,使用boolean数组不一定管用。
1178. 猜字谜,这道题,我就是使用了位运算,才能在不超时的情况下完成。
Map<Integer, Integer> scoreMap = new HashMap<Integer, Integer>();
public List<Integer> findNumOfValidWords(String[] words, String[] puzzles) {
for (int i = 0; i < words.length; i++) {
//把字符串变成一个int类型
int score = getScore(words[i]);
int value = 1;
//把相同的放在一起
if (scoreMap.containsKey(score)) {
value += scoreMap.get(score);
}
scoreMap.put(score, value);
}
List<Integer> res = new ArrayList<Integer>();
for (int i = 0; i < puzzles.length; i++) {
res.add(getAns(puzzles[i]));
}
return res;
}
//这个操作就是字符串转int(题目很明显,word中单词的个数是无所谓的,只有有就行)
private int getScore(String word) {
int ans = 0;
for (int i = 0; i < word.length(); i++) {
ans |= 1 << (word.charAt(i) - 'a');
}
return ans;
}
private int getAns(String puzz) {
int ans = 0;
int key = 1 << (puzz.charAt(0) - 'a');
//能想到这一步就比较复杂了,puzzles[i].length == 7给了很好的提示,那就是把所有的情况都考虑起来,总共的可能性也就2的6次方种
//这时候的i就相当于一个6位的boolean类型的数组
for (int i = 0; i < (1 << (puzz.length() - 1)); i++) {
//因为必须包含首字母,所以key必须加上
int k = key;
for (int j = 1; j < puzz.length(); j++) {
//使用位运算,来判断,当前数字是否该算上
if ((i & (1 << (j - 1))) != 0) {
k += (1 << (puzz.charAt(j) - 'a'));
}
}
//答案中时包含k的,那就加上
if (scoreMap.containsKey(k)) {
ans += scoreMap.get(k);
}
}
return ans;
}