1160. Find Words That Can Be Formed by Characters - Explanation
Description
You are given an array of strings words and a string chars.
A string is good if it can be formed by characters from chars (each character can only be used once for each word in words).
Return the sum of lengths of all good strings in words.
Example 1:
Input: words = ["cat","bt","hat","tree"], chars = "atach"
Output: 6Explanation: The strings that can be formed are "cat" and "hat" so the answer is 3 + 3 = 6.
Example 2:
Input: words = ["hello","world","leetcode"], chars = "welldonehoneyr"
Output: 10Explanation: The strings that can be formed are "hello" and "world" so the answer is 5 + 5 = 10.
Constraints:
1 <= words.length <= 10001 <= words[i].length, chars.length <= 100words[i]andcharsconsist of lowercase English letters.
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Hash Maps/Dictionaries - Counting character frequencies and performing lookups
- String Iteration - Traversing characters in strings to build frequency counts
- Arrays as Fixed-Size Maps - Using arrays of size 26 for lowercase letter counting as an optimization
1. Hash Map (Two Pass)
Intuition
A word can be formed from chars if every character in the word appears in chars with at least the same frequency. We first count character frequencies in chars, then for each word, count its character frequencies and verify that chars has enough of each character.
Algorithm
- Build a frequency map
countfor all characters inchars. - For each word:
- Build a frequency map
cur_wordfor the word. - Check if every character in
cur_wordhas a count less than or equal to its count incount. - If valid, add the word's length to
res.
- Build a frequency map
- Return the total length.
class Solution:
def countCharacters(self, words: List[str], chars: str) -> int:
count = Counter(chars)
res = 0
for w in words:
cur_word = Counter(w)
good = True
for c in cur_word:
if cur_word[c] > count[c]:
good = False
break
if good:
res += len(w)
return resTime & Space Complexity
- Time complexity:
- Space complexity: since we have at most different characters.
Where is the length of , is the number of words and is the average length of each word.
2. Hash Map (One Pass)
Intuition
Instead of building the complete frequency map for each word first, we can check character availability on the fly. As we iterate through each character of a word, we increment its count in a temporary map and immediately check if it exceeds the available count in chars. This allows early termination if a word is invalid.
Algorithm
- Build a frequency map
countfor all characters inchars. - For each word:
- Initialize an empty frequency map
cur_word. - For each character in the word:
- Increment its count in
cur_word. - If it exceeds the count in
count, mark the word as invalid and break.
- Increment its count in
- If the word is valid, add its length to
res.
- Initialize an empty frequency map
- Return the total length.
class Solution:
def countCharacters(self, words: List[str], chars: str) -> int:
count = Counter(chars)
res = 0
for w in words:
cur_word = defaultdict(int)
good = True
for c in w:
cur_word[c] += 1
if cur_word[c] > count[c]:
good = False
break
if good:
res += len(w)
return resTime & Space Complexity
- Time complexity:
- Space complexity: since we have at most different characters.
Where is the length of , is the number of words and is the average length of each word.
3. Hash Table
Intuition
Since we only have lowercase letters, we can use a fixed-size array of 26 elements instead of a hash map. This is more cache-friendly and avoids hash function overhead. We decrement counts as we use characters and reset the array for each new word using a stored original copy.
Algorithm
- Create an array
countof size 26 and populate it with frequencies fromchars. - Store a copy of
countasorgfor resetting. - For each word:
- For each character, decrement
count[c - 'a']. - If it goes negative, the word is invalid; break early.
- If valid, add the word's length to
res. - Reset
counttoorgfor the next word.
- For each character, decrement
- Return the total length.
class Solution:
def countCharacters(self, words: List[str], chars: str) -> int:
count = [0] * 26
for c in chars:
count[ord(c) - ord('a')] += 1
org = count[:]
res = 0
for w in words:
good = True
for c in w:
i = ord(c) - ord('a')
count[i] -= 1
if count[i] < 0:
good = False
break
if good:
res += len(w)
for i in range(26):
count[i] = org[i]
return resTime & Space Complexity
- Time complexity:
- Space complexity: since we have at most different characters.
Where is the length of , is the number of words and is the average length of each word.
Common Pitfalls
Modifying the Original Character Count Map
When checking each word, you must use a fresh copy of the character counts or reset after each word. A common mistake is decrementing the original count map without restoring it, causing subsequent words to have fewer available characters than they should.
Checking Character Presence Without Frequency
It is not enough to check if a character exists in chars; you must verify that the frequency of each character in the word does not exceed its frequency in chars. For example, "aab" cannot be formed from "ab" even though both 'a' and 'b' are present.
Sign in to join the discussion