1160. Find Words That Can Be Formed by Characters
Problem Description
In this problem, we are given two inputs: an array of strings called words
and a single string called chars
. Our task is to determine which strings in the words
array are "good". A "good" string is one that can be completely formed using the characters in chars
. Each character in chars
may only be used once when forming a word. After identifying all the "good" strings, we must calculate the sum of their lengths and return that sum as the result.
For example, if words = ["cat", "bt", "hat", "tree"]
and chars = "atach"
, only "cat" and "hat" are "good" because they can be formed using the characters in chars
without reusing a single character. The lengths of "cat" and "hat" are 3 and 3, respectively, so the sum is 6. The goal of the problem is to implement a function that can do this calculation for any given words
and chars
.
Intuition
The solution approach can be divided into the following steps:
-
Count Characters in
chars
: We first count the frequency of each character in the stringchars
. This helps us know how many times we can use a particular character while forming words. -
Iterate Over
words
: Next, we loop through each string inwords
and count the frequency of each character in the current string (w
). -
Check if a Word Can be Formed: To determine if a word is "good", we compare character frequencies of the current word with those in
chars
. If for each character in the word, the count inchars
is greater than or equal to the count in the word, the word is "good". -
Calculate and Sum Lengths: For each "good" string found, we add its length to a running total,
ans
. -
Return the Result: Once all words have been checked, return the accumulated sum of lengths.
The beauty of this approach lies in the efficient use of character counting, which allows us to verify if a word can be formed without having to repeatedly search for characters in chars
. By using a Python Counter object, which is essentially a specialized dictionary for counting hashable objects, we perform the needed comparisons succinctly and efficiently. The all
function in Python is then used to ensure that all character counts in the word meet the availability requirement in chars
, which is a very intuitive way to check the condition for every character simultaneously.
Solution Approach
The implementation of the solution uses a Counter
from Python's collections
module, which is a specialized dictionary designed for counting hashable objects. The Counter
data structure is ideal for tracking the frequency of elements in an iterable.
Here is a step-by-step breakdown of how the solution is implemented:
-
Create a Counter for
chars
: First, aCounter
is created for the stringchars
. ThisCounter
object, namedcnt
, will provide a dictionary-like structure where each key is a character fromchars
and its corresponding value is the number of occurrences of that character.cnt = Counter(chars)
-
Initialize an Answer Variable: An integer
ans
is initialized to zero, which will hold the sum of lengths of all "good" strings in the arraywords
.ans = 0
-
Loop Through Each Word in
words
: We iterate over each wordw
in thewords
array. For each word, a newCounter
is created to count the occurrences of characters in that word.for w in words: wc = Counter(w)
-
Check if the Word is "Good": Using the
all
function, we check if every characterc
in the current word has a frequency countv
that is less than or equal to its count incnt
. This ensures that each character required to form the wordw
is available in the quantity needed inchars
.if all(cnt[c] >= v for c, v in wc.items()): ans += len(w)
- If the condition is true for all characters, it means the word can be formed from the characters in
chars
, hence it is a "good" word. We add the length of the word toans
. - We do not modify the original
cnt
Counter because we do not want to affect the counts for the subsequent iteration of words. Instead, we just use its values to check thewc
counts.
- If the condition is true for all characters, it means the word can be formed from the characters in
-
Return the Total Length: After iterating through all the words, the total length of all "good" words is stored in
ans
, which we return.return ans
This algorithm has a time complexity that is dependent on the total number of characters in all words (O(N)
where N
is the total number of characters) since we are counting characters for each word and iterating over each character count. The space complexity is O(U)
where U
is the total number of unique characters in chars
and all words since Counter
objects need to keep track of each unique character and its count.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Consider a small example where we have words = ["hello", "world", "loop"]
and chars = "hloeldorw"
.
-
Step 1: Count Characters in
chars
: We count the frequency of each character:h:1, l:2, o:2, e:1, d:1, r:1, w:1
We have enough characters to potentially make the words "hello", "world", and "loop".
-
Step 2: Initialize an Answer Variable: We initialize
ans
to zero:ans = 0
-
Step 3: Iterate Over
words
: We iterate over each word:-
For "hello":
- Count characters:
h:1, e:1, l:2, o:1
- We check each character against our
chars
count and see that we can form "hello" withchars
. Since "hello" is a "good" word, we add its length (5) toans
:ans += 5 # ans = 5
- Count characters:
-
For "world":
- Count characters:
w:1, o:1, r:1, l:1, d:1
- All characters are present in our
chars
count. "world" is also a "good" word, so we add its length (5) toans
:ans += 5 # ans = 10
- Count characters:
-
For "loop":
- Count characters:
l:1, o:2, p:1
- We have only 2 'o's and 1 'l' in
chars
, not enough to form the word "loop", so we do not add anything toans
for this word.
- Count characters:
-
-
Step 4: Return the Total Length: After processing all the words, the value of
ans
is 10 (5+5). This is the sum of lengths of all "good" words. We return this as the final answer:return ans # ans = 10
The sum of lengths of all "good" words that can be formed by the given chars
is 10 in this example.
Solution Implementation
1from collections import Counter # Import the Counter class from the collections module
2
3class Solution:
4 def countCharacters(self, words: List[str], chars: str) -> int:
5 # Count the frequency of each character in the given string 'chars'
6 char_count = Counter(chars)
7 # Initialize answer to hold the total length of all words that can be formed
8 total_length = 0
9
10 # Iterate through each word in the list of words
11 for word in words:
12 # Count the frequency of each character in the current word
13 word_count = Counter(word)
14 # Check if the word can be formed by the chars in 'chars'
15 if all(char_count[char] >= count for char, count in word_count.items()):
16 total_length += len(word) # If it can be formed, add the word's length to the total
17
18 # Return the total length of all words that can be formed
19 return total_length
20
1class Solution {
2 public int countCharacters(String[] words, String chars) {
3 // Array to store the frequency of each character in 'chars'
4 int[] charFrequency = new int[26];
5
6 // Count the frequency of each character in 'chars'
7 for (int i = 0; i < chars.length(); ++i) {
8 charFrequency[chars.charAt(i) - 'a']++;
9 }
10
11 // Variable to hold the total length of all words that can be formed
12 int totalLength = 0;
13
14 // Iterate over each word in the array 'words'
15 for (String word : words) {
16 // Array to store the frequency of each character in the current word
17 int[] wordFrequency = new int[26];
18 // Flag to check if the current word can be formed
19 boolean canBeFormed = true;
20
21 // Count the frequency of each character in the current word
22 for (int i = 0; i < word.length(); ++i) {
23 int index = word.charAt(i) - 'a';
24
25 // If the character frequency exceeds that in 'chars', the word can't be formed
26 if (++wordFrequency[index] > charFrequency[index]) {
27 canBeFormed = false;
28 break; // Break out of the loop as the current word can't be formed
29 }
30 }
31
32 // If the word can be formed, add its length to the totalLength
33 if (canBeFormed) {
34 totalLength += word.length();
35 }
36 }
37
38 // Return the total length of all words that can be formed
39 return totalLength;
40 }
41}
42
1#include <vector>
2#include <string>
3
4class Solution {
5public:
6 // Determines the sum of lengths of all words that can be formed by characters in 'chars'
7 int countCharacters(vector<string>& words, string chars) {
8 // Frequency array for characters in 'chars'
9 int charCount[26] = {0};
10 // Fill the frequency array
11 for (char& ch : chars) {
12 ++charCount[ch - 'a'];
13 }
14
15 int totalLength = 0; // This will hold the sum of lengths of words that can be formed
16
17 // Iterate over each word in the list
18 for (auto& word : words) {
19 // Frequency array for characters in the current word
20 int wordCount[26] = {0};
21 bool canFormWord = true; // Flag to check if the word can be formed
22
23 // Check if each character in the word can be formed by the characters in 'chars'
24 for (char& ch : word) {
25 int index = ch - 'a';
26 // If the current character exceeds the frequency in 'chars', the word can't be formed
27 if (++wordCount[index] > charCount[index]) {
28 canFormWord = false;
29 break;
30 }
31 }
32
33 // If the word can be formed, add its length to the totalLength
34 if (canFormWord) {
35 totalLength += word.size();
36 }
37 }
38
39 return totalLength; // Return the total sum of lengths of all words that can be formed
40 }
41};
42
1function countCharacters(words: string[], chars: string): number {
2 // Function to get index of character in the alphabet array (0-25)
3 const getIndex = (char: string): number => char.charCodeAt(0) - 'a'.charCodeAt(0);
4
5 // Initialize an array to store the frequency of each character in 'chars'
6 const charCount = new Array(26).fill(0);
7 // Fill the charCount array with the frequency of each character in 'chars'
8 for (const char of chars) {
9 charCount[getIndex(char)]++;
10 }
11
12 // Initialize a variable to keep track of the total length of all valid words
13 let totalLength = 0;
14
15 // Iterate over each word in the 'words' array to check if it can be formed
16 for (const word of words) {
17 // Initialize an array to store the frequency of each character in the current word
18 const wordCount = new Array(26).fill(0);
19 // Flag to check if the current word can be formed
20 let canBeFormed = true;
21
22 // Iterate over each character in the word to update wordCount and check if it can be formed
23 for (const char of word) {
24 wordCount[getIndex(char)]++;
25 // If the character's frequency in word exceeds that in 'chars', set the flag to false
26 if (wordCount[getIndex(char)] > charCount[getIndex(char)]) {
27 canBeFormed = false;
28 break;
29 }
30 }
31
32 // If the word can be formed, add its length to the totalLength
33 if (canBeFormed) {
34 totalLength += word.length;
35 }
36 }
37
38 // Return the total length of all words that can be formed using 'chars'
39 return totalLength;
40}
41
Time and Space Complexity
Time Complexity
The time complexity of the code can be analyzed as follows:
-
The creation of the
cnt
counter from thechars
string: this operation takesO(m)
, wherem
is the length of thechars
string since we must count the frequency of each character inchars
. -
The for-loop iterates over each word in the
words
list. Let the length of the list ben
and the average length of the words bek
. -
Inside the loop, a new counter
wc
is created for each word: this operation also has a complexity ofO(k)
. -
The
all
function checks if all characters inw
have a count less or equal to their count incnt
. This operation isO(k)
as it checks each character's count (up to the total character count of a word).
Since steps 3 and 4 are within the loop iteration, they will run n
times, which makes that part of the algorithm O(n*k)
.
Summing these up, the total time complexity is O(m + n*k)
.
Space Complexity
The space complexity can be analyzed as follows:
-
The
cnt
counter forchars
utilizesO(u)
space, whereu
is the unique number of characters inchars
. -
The
wc
counter for each word similarly utilizesO(v)
space in the worst case, wherev
is the unique number of characters in that word. -
However, since
wc
is constructed for one word at a time,O(v)
space will be reused for each word, andv
is bounded by the fixed size of the alphabet (u
), so we can considerO(u)
space for the counters.
Given that space used by variables like ans
and temporary variables in the iteration is negligible relative to the size of the input, the overall space complexity is dominated by the space required for the counters, which is O(u)
where u
is the number of unique letters in chars
and is bounded by the size of the character set used (e.g., the English alphabet, which has a fixed size of 26).
Therefore, the space complexity is O(u)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!