leetcode 1178.猜字谜
题干
外国友人仿照中国字谜设计了一个英文版猜字谜小游戏,请你来猜猜看吧。
字谜的迷面 puzzle 按字符串形式给出,如果一个单词 word 符合下面两个条件,那么它就可以算作谜底:
单词 word 中包含谜面 puzzle 的第一个字母。
单词 word 中的每一个字母都可以在谜面 puzzle 中找到。
例如,如果字谜的谜面是 “abcdefg”,那么可以作为谜底的单词有 “faced”, “cabbage”, 和 “baggage”;而 “beefed”(不含字母 “a”)以及 “based”(其中的 “s” 没有出现在谜面中)。
返回一个答案数组 answer,数组中的每个元素 answer[i] 是在给出的单词列表 words 中可以作为字谜迷面 puzzles[i] 所对应的谜底的单词数目。
示例:
输入:
words = [“aaaa”,“asas”,“able”,“ability”,“actt”,“actor”,“access”],
puzzles = [“aboveyz”,“abrodyz”,“abslute”,“absoryz”,“actresz”,“gaswxyz”]
输出:[1,1,3,2,4,0]
解释:
1 个单词可以作为 “aboveyz” 的谜底 : “aaaa”
1 个单词可以作为 “abrodyz” 的谜底 : “aaaa”
3 个单词可以作为 “abslute” 的谜底 : “aaaa”, “asas”, “able”
2 个单词可以作为 “absoryz” 的谜底 : “aaaa”, “asas”
4 个单词可以作为 “actresz” 的谜底 : “aaaa”, “asas”, “actt”, “access”
没有单词可以作为 “gaswxyz” 的谜底,因为列表中的单词都不含字母 ‘g’。
提示:
1 <= words.length <= 10^5
4 <= words[i].length <= 50
1 <= puzzles.length <= 10^4
puzzles[i].length == 7
words[i][j], puzzles[i][j] 都是小写英文字母。
每个 puzzles[i] 所包含的字符都不重复。
题解
直接模拟,用数组映射26个字母的出现次数,显然超时了
class Solution {
public:
vector<int> findNumOfValidWords(vector<string>& words, vector<string>& puzzles) {
int puzzleCount = puzzles.size();
int wordCount = words.size();
vector<vector<int> > puzzleStat(puzzleCount,vector<int>(26,0));
vector<vector<int> > wordStat(wordCount,vector<int>(26,0));
vector<int> ans(puzzleCount);
for(int i = 0 ; i < puzzleCount ; ++i){
for(auto j : puzzles[i]){
puzzleStat[i][j - 'a']++;
}
}
for(int i = 0 ; i < wordCount ; ++i){
for(auto j : words[i]){
wordStat[i][j - 'a']++;
}
}
for(int i = 0 ; i < puzzleCount ; ++i){
int ansCount = 0;
for(int j = 0 ; j < wordCount ; ++j){
//如果j标words中有谜面的首字母
if(wordStat[j][puzzles[i][0] - 'a'] != 0){
bool flag = true;
for(int k = 0 ; k < 26 ; ++k){
//如果j标words中存在的字母在谜面中bu存在
if(wordStat[j][k] != 0 && puzzleStat[i][k] == 0){
flag = false;
break;
}
}
if(flag){
ansCount++;
}
}
}
ans[i] = ansCount;
}
return ans;
}
};
思考一下位运算的可能性,2^26 = 67108864 < INT_MAX
那好办,就不用数组映射字母出现次数了,用一个二进制int来映射就行,因为题目要求的判别并不用判断字母的个数,只要能够表示是否存在就可以了
还就那个继续超时
class Solution {
public:
//获取num从右往左第n位
int getDigit(int num,int n){
num >>= n;
return num & 1;
}
vector<int> findNumOfValidWords(vector<string>& words, vector<string>& puzzles) {
int puzzleCount = puzzles.size();
int wordCount = words.size();
vector<int> puzzleStat(puzzleCount,0);
vector<int> wordStat(wordCount,0);
vector<int> ans(puzzleCount);
for(int i = 0 ; i < puzzleCount ; ++i){
int tempCount = 0;
for(auto j : puzzles[i]){
puzzleStat[i] |= 1 << (j - 'a');
}
}
for(int i = 0 ; i < wordCount ; ++i){
for(auto j : words[i]){
wordStat[i] |= 1 << (j - 'a');
}
}
for(int i = 0 ; i < puzzleCount ; ++i){
int ansCount = 0;
for(int j = 0 ; j < wordCount ; ++j){
//如果j标words中有谜面的首字母
//out<<getDigit(wordStat[j],puzzles[i][0] - 'a')<<endl;
if(getDigit(wordStat[j],puzzles[i][0] - 'a') != 0){
//如果words中存在的字母谜面中不存在
//cout<<wordStat[j]<<' '<<puzzleStat[i]<<' '<<(wordStat[j] & puzzleStat[i])<<endl;
if((wordStat[j] & puzzleStat[i]) == wordStat[j]){
ansCount++;
}
}
}
ans[i] = ansCount;
}
return ans;
}
};
那就只能反过来遍历了,根据谜面枚举谜面的所有二进制子集(因为谜面只有7位),然后把words对应的二进制信息放到哈希表中,通过谜面二进制的子集元素来检查哈希表
class Solution {
public:
vector<int> findNumOfValidWords(vector<string>& words, vector<string>& puzzles) {
int puzzleCount = puzzles.size();
int wordCount = words.size();
unordered_map<int,int> wordsMask;
vector<int> ans(puzzleCount,0);
for(int i = 0 ; i < wordCount ; ++i){
int tempMask = 0;
for(auto j : words[i]){
tempMask |= 1 << (j - 'a');
}
if (__builtin_popcount(tempMask) <= 7) {
wordsMask[tempMask]++;
}
}
for(int i = 0 ; i < puzzleCount ; ++i){
int sum = 0;
int puzzlesMask = 0;
for(auto j : puzzles[i]){
puzzlesMask |= 1 << (j - 'a');
}
int tempMask = puzzlesMask;
do{
int index = tempMask | (1 << (puzzles[i][0] - 'a') );
if(wordsMask.count(index)){
sum += wordsMask[index];
}
tempMask = (tempMask - 1) & puzzlesMask;
}while(tempMask != puzzlesMask);
ans[i] = sum / 2;
}
return ans;
}
};
关键还就是这手二进制子集遍历:
int tempMask = puzzlesMask;
do{
int index = tempMask | (1 << (puzzles[i][0] - 'a') );
if(wordsMask.count(index)){
sum += wordsMask[index];
}
tempMask = (tempMask - 1) & puzzlesMask;
}while(tempMask != puzzlesMask);
有点写呲了,这里我的写法把整个谜面取进了mask,导致在后面遍历子集的时候连带着首位的1在遍历,结果就是统计个数翻了一番。
for(auto j : puzzles[i]){
puzzlesMask |= 1 << (j - 'a');
}
这里如果改写成下标,不取第一位的话,后面sum就不用/2了
for(int j = 1 ; j < 7 ; ++j){
puzzlesMask |= 1 << (puzzles[i][j] - 'a');
}