Hard
题目描述
对于给定的谜题字符串,如果一个单词满足以下两个条件,则该单词有效:
- 单词包含谜题的第一个字母。
- 单词中的每个字母都在谜题中。
例如,如果谜题是 “abcdefg”,那么有效的单词是 “faced”、“cabbage” 和 “baggage”,而无效的单词是 “beefed”(不包含 ‘a’)和 “based”(包含不在谜题中的 ’s’)。
返回一个数组 answer,其中 answer[i] 是给定单词列表 words 中对于谜题 puzzles[i] 有效的单词数量。
示例 1:
输入: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'。
示例 2:
输入:words = ["apple","pleas","please"], puzzles = ["aelwxyz","aelpxyz","aelpsxy","saelpxy","xaelpsy"]
输出:[0,1,3,2,0]
约束条件:
- 1 <= words.length <= 10^5
- 4 <= words[i].length <= 50
- 1 <= puzzles.length <= 10^4
- puzzles[i].length == 7
- words[i] 和 puzzles[i] 由小写英文字母组成
- 每个 puzzles[i] 不包含重复字符
解题思路
这道题的关键在于利用谜题长度只有7这个特点,使用位运算来优化解法。
思路分析:
位掩码表示:由于字母只有26个,我们可以用一个整数的二进制位来表示字符串中包含哪些字母。比如包含字母’a’就设置第0位为1,包含’b’就设置第1位为1。
有效条件转换:
- 单词必须包含谜题的首字母
- 单词的所有字母都必须在谜题中(即单词的位掩码是谜题位掩码的子集)
优化策略:对于每个谜题,我们需要枚举所有包含首字母的子集。由于谜题长度固定为7,最多有2^6=64个这样的子集,这比直接遍历所有单词要高效得多。
算法步骤:
- 预处理所有单词,计算每个单词的位掩码,并用哈希表统计每种位掩码的出现次数
- 对每个谜题,枚举所有包含首字母的子集
- 对每个子集,在哈希表中查找对应的单词数量并累加
这种方法的时间复杂度是O(|words| + |puzzles| × 2^6),空间复杂度是O(|words|)。
代码实现
class Solution {
public:
vector<int> findNumOfValidWords(vector<string>& words, vector<string>& puzzles) {
unordered_map<int, int> wordMasks;
// 预处理所有单词的位掩码
for (const string& word : words) {
int mask = 0;
for (char c : word) {
mask |= 1 << (c - 'a');
}
wordMasks[mask]++;
}
vector<int> result;
for (const string& puzzle : puzzles) {
int puzzleMask = 0;
for (char c : puzzle) {
puzzleMask |= 1 << (c - 'a');
}
int firstLetterBit = 1 << (puzzle[0] - 'a');
int count = 0;
// 枚举所有包含首字母的子集
int subset = puzzleMask;
do {
if (subset & firstLetterBit) {
count += wordMasks[subset];
}
subset = (subset - 1) & puzzleMask;
} while (subset);
result.push_back(count);
}
return result;
}
};
class Solution:
def findNumOfValidWords(self, words: List[str], puzzles: List[str]) -> List[int]:
from collections import defaultdict
word_masks = defaultdict(int)
# 预处理所有单词的位掩码
for word in words:
mask = 0
for c in word:
mask |= 1 << (ord(c) - ord('a'))
word_masks[mask] += 1
result = []
for puzzle in puzzles:
puzzle_mask = 0
for c in puzzle:
puzzle_mask |= 1 << (ord(c) - ord('a'))
first_letter_bit = 1 << (ord(puzzle[0]) - ord('a'))
count = 0
# 枚举所有包含首字母的子集
subset = puzzle_mask
while True:
if subset & first_letter_bit:
count += word_masks[subset]
if subset == 0:
break
subset = (subset - 1) & puzzle_mask
result.append(count)
return result
public class Solution {
public IList<int> FindNumOfValidWords(string[] words, string[] puzzles) {
var wordMasks = new Dictionary<int, int>();
// 预处理所有单词的位掩码
foreach (string word in words) {
int mask = 0;
foreach (char c in word) {
mask |= 1 << (c - 'a');
}
wordMasks[mask] = wordMasks.GetValueOrDefault(mask, 0) + 1;
}
var result = new List<int>();
foreach (string puzzle in puzzles) {
int puzzleMask = 0;
foreach (char c in puzzle) {
puzzleMask |= 1 << (c - 'a');
}
int firstLetterBit = 1 << (puzzle[0] - 'a');
int count = 0;
// 枚举所有包含首字母的子集
int subset = puzzleMask;
do {
if ((subset & firstLetterBit) != 0) {
count += wordMasks.GetValueOrDefault(subset, 0);
}
subset = (subset - 1) & puzzleMask;
} while (subset != 0);
result.Add(count);
}
return result;
}
}
var findNumOfValidWords = function(words, puzzles) {
const wordMasks = new Map();
// 预处理所有单词的位掩码
for (const word of words) {
let mask = 0;
for (const c of word) {
mask |= 1 << (c.charCodeAt(0) - 97);
}
wordMasks.set(mask, (wordMasks.get(mask) || 0) + 1);
}
const result = [];
for (const puzzle of puzzles) {
let puzzleMask = 0;
for (const c of puzzle) {
puzzleMask |= 1 << (c.charCodeAt(0) - 97);
}
const firstLetterBit = 1 << (puzzle[0].charCodeAt(0) - 97);
let count = 0;
// 枚举所有包含首字母的子集
let subset = puzzleMask;
do {
if (subset & firstLetterBit) {
count += wordMasks.get(subset) || 0;
}
subset = (subset - 1) & puzzleMask;
} while (subset);
result.push(count);
}
return result;
};
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(W + P × 2^6) | W为单词总数,P为谜题数量,每个谜题最多枚举64个子集 |
| 空间复杂度 | O(W) | 用于存储单词位掩码的哈希表 |