【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]
解答
class Solution {
public:
vector<int> findNumOfValidWords(vector<string>& words, vector<string>& puzzles) {
unordered_map<int, int> ump; // key为word压缩后的状态,value为该状态出现的次数
vector<int> res;
for (auto& word : words) {
int k = 0;
for (auto i : word) // 每个word压缩状态存入哈希表
k |= (1 << i - 'a');
ump[k]++;
}
for (auto& puzzle : puzzles) {
int k = 0;
int ans = 0;
for (int i = 1; i < puzzle.size(); i++) // 除去首位,剩余位的二进制表示
k |= (1 << puzzle[i] - 'a');
int sub = k, fir = 1 << puzzle[0] - 'a';
do {
ans += ump[fir | sub]; // 首位 + 子集
sub = (sub - 1) & k; // 求k的子集
} while (sub != k); // -1 & k = k
res.push_back(ans);
}
return res;
}
};
重点思路
本题考查两个知识点:状态压缩、生成子集。
状态压缩非常简单,只需要理解k |= (1 << puzzle[i] - 'a')
这一行代码即可。其中|=
指或运算,所有两边有一个1则为1;1 << puzzle[i] - 'a'
是指将1的二进制表示左移 puzzle[i] - 'a'
位。
根据题目要求,puzzle的首位必须包含在word中,则将首位单独提出来,其他位生成完备的子集进行比较。
生成子集的方法有很多,这里采用了二进制子集生成的一种非常巧妙的方法,核心是sub = (sub - 1) & k
,其中,sub
是k
的子集。&k
保证sub为k的子集,sub - 1
保证每次生成的sub一定严格单调递减,递减的间隔为最小值1保证生成完整的子集。需要注意循环的策略,不要忽略sub = 0
和sub = k
的情况。