Find Words That Can Be Formed by Characters
EasyPrompt
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: 6
Explanation: 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: 10
Explanation: 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.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach iterates through each word and, for each word, checks if it can be formed from the characters in chars. It does this by creating a temporary list of characters from the chars string for each word. Then, for every character in the word, it tries to find and remove it from this temporary list. If any character from the word cannot be found in the list, the word is not "good". If all characters are found, the word is "good", and its length is added to the total.
Algorithm
- Initialize
sum = 0. - For each
wordinwords:- Create a
List<Character>from thecharsstring. - Initialize a boolean
canForm = true. - For each character
cinword:- If
cis present in the list, remove one occurrence ofc. - Else, set
canForm = falseand break the inner loop.
- If
- If
canFormistrue, addword.length()tosum.
- Create a
- Return
sum.
Walkthrough
The algorithm starts by initializing a sum variable to zero. It then enters a loop that processes each word from the input words array. Inside this loop, a mutable copy of the chars string is created, typically as a List<Character>. This is necessary because we need to "use up" characters for each word independently. A boolean flag, say isGood, is set to true. A nested loop iterates through each character of the current word. In this inner loop, we check if the character exists in our list copy of chars. If it exists, we remove one instance of that character from the list to simulate its usage. If it does not exist, the word cannot be formed. We set isGood to false and immediately break out of the inner loop, as there's no need to check further for this word. After the inner loop finishes, we check the isGood flag. If it's still true, it means all characters of the word were found in chars, so we add the length of the word to our running sum. Finally, after checking all words, the total sum is returned.
import java.util.ArrayList;import java.util.List; public class Solution { public int countCharacters(String[] words, String chars) { int goodWordsLengthSum = 0; for (String word : words) { List<Character> charList = new ArrayList<>(); for (char c : chars.toCharArray()) { charList.add(c); } boolean isGood = true; for (char wordChar : word.toCharArray()) { // In Java, list.remove(Object) is needed to remove the element, not by index. if (charList.remove(Character.valueOf(wordChar))) { // Character was found and removed. } else { // Character was not found. isGood = false; break; } } if (isGood) { goodWordsLengthSum += word.length(); } } return goodWordsLengthSum; }}Complexity
Time
O(N * M * C), where `N` is the number of words, `M` is the average length of a word, and `C` is the length of `chars`. For each of the `N` words, we iterate through its `M` characters. For each character, `list.remove()` takes `O(C)` time. This leads to a high time complexity.
Space
O(C), where `C` is the length of the `chars` string. A new list of characters of size `C` is created for each word in the `words` array.
Trade-offs
Pros
Simple to conceptualize and implement without advanced data structures.
Follows the problem description literally, making it easy to reason about.
Cons
Highly inefficient, especially for long
charsstrings and many words.Repeatedly creating and scanning the character list is very slow.
The
containsandremoveoperations on anArrayListare linear in time, leading to poor overall performance.
This is a highly efficient approach that uses frequency arrays to solve the problem in linear time. First, we count the frequency of each character in the chars string and store it in an array of size 26. Then, for each word, we also count its character frequencies and check if it can be formed by comparing its frequency map against the available characters' frequency map. A word is "good" if the count of each character it requires is less than or equal to the count of that character available in chars.
Algorithm
- Create an integer array
charsFreqof size 26, initialized to zeros. - Iterate through each character
cincharsand incrementcharsFreq[c - 'a']. - Initialize
sum = 0. - For each
wordinwords:- Create a temporary integer array
wordFreqof size 26. - Iterate through each character
cinwordand incrementwordFreq[c - 'a']. - Initialize a boolean
canForm = true. - Iterate from
i = 0to25. IfwordFreq[i] > charsFreq[i], setcanForm = falseand break. - If
canFormistrue, addword.length()tosum.
- Create a temporary integer array
- Return
sum.
Walkthrough
The core idea is to move from character-by-character searching to a frequency-based comparison. Since the input consists of only lowercase English letters, an integer array of size 26 is a perfect and efficient tool for a frequency map.
First, we create an integer array, charsFreq, of size 26. We iterate through the chars string once and populate this array. For each character c, we increment the count at index c - 'a'. This gives us a complete count of available characters in O(C) time, where C is the length of chars.
Next, we initialize a totalLength sum to 0 and iterate through each word in the words array. For each word, we build its own frequency map, wordFreq, in the same way. After building the wordFreq map, we compare it against the charsFreq map. We loop from 0 to 25 (representing 'a' to 'z') and check if wordFreq[i] is greater than charsFreq[i] for any i. If it is, the word requires more of a certain character than available, so it's not a "good" word. If we complete the comparison loop without finding such a case, the word is "good", and we add its length to our totalLength sum. This process avoids any expensive string or list operations within the main loop, making it very fast.
public class Solution { public int countCharacters(String[] words, String chars) { int[] charsFreq = new int[26]; for (char c : chars.toCharArray()) { charsFreq[c - 'a']++; } int goodWordsLengthSum = 0; for (String word : words) { int[] wordFreq = new int[26]; for (char c : word.toCharArray()) { wordFreq[c - 'a']++; } boolean isGood = true; for (int i = 0; i < 26; i++) { if (wordFreq[i] > charsFreq[i]) { isGood = false; break; } } if (isGood) { goodWordsLengthSum += word.length(); } } return goodWordsLengthSum; }}Complexity
Time
O(C + S), where `C` is the length of `chars` and `S` is the sum of lengths of all words in `words`. Building `charsFreq` takes `O(C)`. Then, processing all words involves iterating through each character once to build frequency maps, which takes `O(S)` in total. The final comparison for each word takes O(26), which is constant time. This results in an overall linear time complexity relative to the total input size.
Space
O(1). We use a few fixed-size arrays of size 26. Since the alphabet size is constant, the space required does not scale with the input size.
Trade-offs
Pros
Extremely efficient with linear time complexity.
Optimal solution for the given constraints.
Uses a simple and fast data structure (array) for frequency counting.
Cons
Slightly more complex to write than the naive approach.
The constant space usage is specific to a small, fixed character set (like lowercase English letters). It would be less efficient for a very large character set where a HashMap would be needed instead of an array.
Solutions
Solution
class Solution { public int countCharacters ( String [] words , String chars ) { int [] cnt = new int [ 26 ]; for ( int i = 0 ; i < chars . length (); ++ i ) { ++ cnt [ chars . charAt ( i ) - 'a' ]; } int ans = 0 ; for ( String w : words ) { int [] wc = new int [ 26 ]; boolean ok = true ; for ( int i = 0 ; i < w . length (); ++ i ) { int j = w . charAt ( i ) - 'a' ; if (++ wc [ j ] > cnt [ j ]) { ok = false ; break ; } } if ( ok ) { ans += w . length (); } } return ans ; } }class Solution { public: int countCharacters ( vector < string >& words , string chars ) { int cnt [ 26 ]{}; for ( char & c : chars ) { ++ cnt [ c - 'a' ]; } int ans = 0 ; for ( auto & w : words ) { int wc [ 26 ]{}; bool ok = true ; for ( auto & c : w ) { int i = c - 'a' ; if ( ++ wc [ i ] > cnt [ i ]) { ok = false ; break ; } } if ( ok ) { ans += w . size (); } } return ans ; } };class Solution : def countCharacters ( self , words : List [ str ], chars : str ) -> int : cnt = Counter ( chars ) ans = 0 for w in words : wc = Counter ( w ) if all ( cnt [ c ] >= v for c , v in wc . items ()): ans += len ( w ) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.