916. Word Subsets - Explanation
Prerequisites
Before attempting this problem, you should be comfortable with:
- Character Frequency Counting - Using arrays or hash maps to count occurrences of each character in a string
- Hash Maps - For efficient frequency lookups and comparisons
- Greedy Optimization - Combining multiple constraints into a single merged requirement to reduce comparisons
1. Brute Force
Intuition
A word from words1 is "universal" if every word in words2 is a subset of it. For a word to be a subset of another, every character must appear at least as many times in the target word. The straightforward approach is to check each word in words1 against all words in words2, comparing character frequencies to determine if all words2 words are subsets of words1.
Algorithm
- Iterate through each word
w1inwords1. - Count the frequency of each character in
w1. - For each word
w2inwords2, count its character frequencies. - Compare the counts: if any character in
w2appears more times than inw1, markw1as not universal andbreak. - If
w1passes all subset checks, add it to thereslist. - Return the list of universal words.
class Solution:
def wordSubsets(self, words1: List[str], words2: List[str]) -> List[str]:
res = []
for w1 in words1:
count1 = Counter(w1)
is_subset = True
for w2 in words2:
count2 = Counter(w2)
for c in count2:
if count2[c] > count1[c]:
is_subset = False
break
if not is_subset: break
if is_subset:
res.append(w1)
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space, since we have at most different characters.
- space for the output list.
Where is the size of the array , is the length of the longest word in , is the size of the array , and is the length of the longest word in .
2. Greedy + Hash Map
Intuition
Instead of checking every word in words2 for each candidate, we can precompute a single "maximum requirement" array. The key insight is that if a word is universal, it must satisfy all words in words2 simultaneously. This means for each character, we only need the maximum count required across all words in words2. By merging all requirements into one frequency map, we reduce the problem to a single comparison per candidate word.
Algorithm
- Build a combined frequency map for
words2: for each character, store the maximum count needed across all words. - Iterate through each word
winwords1. - Count the frequency of each character in
w. - Compare against the combined requirement: if any character count in
wis less than required,skipthis word. - If
wmeets all requirements, add it to thereslist. - Return the list of universal words.
class Solution:
def wordSubsets(self, words1: List[str], words2: List[str]) -> List[str]:
count_2 = defaultdict(int)
for w in words2:
count_w = Counter(w)
for c, cnt in count_w.items():
count_2[c] = max(count_2[c], cnt)
res = []
for w in words1:
count_w = Counter(w)
flag = True
for c, cnt in count_2.items():
if count_w[c] < cnt:
flag = False
break
if flag:
res.append(w)
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space, since we have at most different characters.
- space for the output list.
Where is the size of the array , is the length of the longest word in , is the size of the array , and is the length of the longest word in .