【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,其中,subk的子集。&k保证sub为k的子集,sub - 1保证每次生成的sub一定严格单调递减,递减的间隔为最小值1保证生成完整的子集。需要注意循环的策略,不要忽略sub = 0sub = k的情况。

posted @ 2021-02-26 21:43  tmpUser  阅读(77)  评论(0编辑  收藏  举报