You are given two arrays: words
and puzzles
.
For each puzzle in puzzles
, you need to find out how many words in words
are valid with respect to that puzzle. A word is considered valid for a puzzle if:
i
-th element is the number of valid words for the i
-th puzzle.
At first glance, the problem suggests checking every word against every puzzle, but this would be too slow for large inputs. To be valid, a word must:
The solution relies on bit manipulation and hash maps for efficiency. Here’s a step-by-step breakdown:
Sample Input:
words = ["apple", "pleas", "please"]
puzzles = ["aelwxyz", "aelpxyz", "aelpsxy", "saelpxy"]
Step-by-Step:
[1, 2, 2, 2]
Brute-force approach:
The key to solving this problem efficiently is to leverage the small size of each puzzle (7 unique letters) and use bitmasking to represent sets of letters. By pre-processing the words into a frequency map of bitmasks, we can quickly count valid words for each puzzle by enumerating all possible subsets of the puzzle's letters that include the first letter. This approach is elegant because it transforms a seemingly intractable problem into one that is both efficient and easy to understand using bit manipulation and hash maps.
from typing import List
from collections import Counter
class Solution:
def findNumOfValidWords(self, words: List[str], puzzles: List[str]) -> List[int]:
def word_to_bitmask(word):
mask = 0
for ch in set(word):
mask |= 1 << (ord(ch) - ord('a'))
return mask
word_count = Counter()
for word in words:
mask = word_to_bitmask(word)
# Only consider words with up to 7 unique letters
if bin(mask).count('1') <= 7:
word_count[mask] += 1
res = []
for puzzle in puzzles:
total = 0
first = 1 << (ord(puzzle[0]) - ord('a'))
# Generate all subsets of puzzle
mask = 0
for ch in puzzle:
mask |= 1 << (ord(ch) - ord('a'))
sub = mask
while sub:
if sub & first:
total += word_count.get(sub, 0)
sub = (sub - 1) & mask
res.append(total)
return res
#include <vector>
#include <string>
#include <unordered_map>
using namespace std;
class Solution {
public:
vector<int> findNumOfValidWords(vector<string>& words, vector<string>& puzzles) {
unordered_map<int, int> word_count;
for (const string& word : words) {
int mask = 0;
for (char ch : word)
mask |= 1 << (ch - 'a');
if (__builtin_popcount(mask) <= 7)
++word_count[mask];
}
vector<int> res;
for (const string& puzzle : puzzles) {
int mask = 0;
for (char ch : puzzle)
mask |= 1 << (ch - 'a');
int first = 1 << (puzzle[0] - 'a');
int total = 0;
int sub = mask;
do {
if (sub & first)
total += word_count[sub];
sub = (sub - 1) & mask;
} while (sub != mask);
res.push_back(total);
}
return res;
}
};
import java.util.*;
class Solution {
public List<Integer> findNumOfValidWords(String[] words, String[] puzzles) {
Map<Integer, Integer> wordCount = new HashMap<>();
for (String word : words) {
int mask = 0;
for (char ch : word.toCharArray())
mask |= 1 << (ch - 'a');
if (Integer.bitCount(mask) <= 7)
wordCount.put(mask, wordCount.getOrDefault(mask, 0) + 1);
}
List<Integer> res = new ArrayList<>();
for (String puzzle : puzzles) {
int mask = 0;
for (char ch : puzzle.toCharArray())
mask |= 1 << (ch - 'a');
int first = 1 << (puzzle.charAt(0) - 'a');
int total = 0;
int sub = mask;
while (true) {
if ((sub & first) != 0)
total += wordCount.getOrDefault(sub, 0);
if (sub == 0) break;
sub = (sub - 1) & mask;
}
res.add(total);
}
return res;
}
}
/**
* @param {string[]} words
* @param {string[]} puzzles
* @return {number[]}
*/
var findNumOfValidWords = function(words, puzzles) {
const wordCount = new Map();
for (const word of words) {
let mask = 0;
for (const ch of new Set(word))
mask |= 1 << (ch.charCodeAt(0) - 97);
if (mask.toString(2).split('1').length - 1 <= 7)
wordCount.set(mask, (wordCount.get(mask) || 0) + 1);
}
const res = [];
for (const puzzle of puzzles) {
let mask = 0;
for (const ch of puzzle)
mask |= 1 << (ch.charCodeAt(0) - 97);
const first = 1 << (puzzle.charCodeAt(0) - 97);
let total = 0;
let sub = mask;
while (sub) {
if (sub & first)
total += wordCount.get(sub) || 0;
sub = (sub - 1) & mask;
}
res.push(total);
}
return res;
};