Count Prefixes of a Given String
EasyPrompt
You are given a string array words and a string s, where words[i] and s comprise only of lowercase English letters.
Return the number of strings in words that are a prefix of s.
A prefix of a string is a substring that occurs at the beginning of the string. A substring is a contiguous sequence of characters within a string.
Example 1:
Input: words = ["a","b","c","ab","bc","abc"], s = "abc"
Output: 3
Explanation:
The strings in words which are a prefix of s = "abc" are:
"a", "ab", and "abc".
Thus the number of strings in words which are a prefix of s is 3.Example 2:
Input: words = ["a","a"], s = "aa"
Output: 2
Explanation:
Both of the strings are a prefix of s.
Note that the same string can occur multiple times in words, and it should be counted each time.
Constraints:
1 <= words.length <= 10001 <= words[i].length, s.length <= 10words[i]andsconsist of lowercase English letters only.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach iterates through each word in the input array words and checks if it is a prefix of the string s. A simple counter is used to keep track of the number of words that satisfy this condition.
Algorithm
-
- Initialize an integer variable
countto 0.
- Initialize an integer variable
-
- Iterate through each
stringin thewordsarray.
- Iterate through each
-
- For each
string, check ifsstarts with it using the built-ins.startsWith(string)method.
- For each
-
- If the check returns
true, it means the current string is a prefix ofs, so incrementcount.
- If the check returns
-
- After the loop finishes, return the final
count.
- After the loop finishes, return the final
Walkthrough
We initialize a counter variable, prefixCount, to zero. We then loop through every word in the words array. For each word, we can use the built-in startsWith() method of the String class to check if s begins with that word. The s.startsWith(word) method efficiently compares the characters of word with the beginning characters of s. If s.startsWith(word) returns true, it means word is a prefix of s, and we increment prefixCount. After checking all the words in the array, the final value of prefixCount is the answer. This method is straightforward to implement and understand, and given the problem's constraints, it is perfectly acceptable.
class Solution { public int countPrefixes(String[] words, String s) { int count = 0; for (String word : words) { if (s.startsWith(word)) { count++; } } return count; }}Complexity
Time
O(N * L), where `N` is the number of words in the `words` array and `L` is the maximum length of a word. For each of the `N` words, the `startsWith` check takes up to `O(L)` time.
Space
O(1), as we only use a single integer variable for the counter, requiring constant extra space.
Trade-offs
Pros
Very simple to understand and implement.
Highly efficient in terms of space complexity.
For the given constraints, it's fast enough and might even outperform more complex solutions due to lower overhead.
Cons
Can be inefficient if the number of words (
N) or the length of the words (L) is very large.It repeatedly scans the beginning of string
sfor each word, which involves redundant comparisons if many words share common prefixes.
This approach uses a Trie (also known as a prefix tree) to optimize the process. First, all words from the words array are inserted into the Trie. Then, we traverse the string s on the Trie and sum up the counts of all words that form a prefix of s.
Algorithm
-
- Define a
TrieNodeclass withchildren(an array of 26TrieNodes) and an integercountto track how many words end at this node.
- Define a
-
- Create a
rootTrieNode.
- Create a
-
- Build Phase: Iterate through each
wordin thewordsarray.
- Build Phase: Iterate through each
-
- For each
word, traverse the Trie from theroot, character by character, creating new nodes if they don't exist.
- For each
-
- At the node corresponding to the last character of the
word, increment itscount.
- At the node corresponding to the last character of the
-
- Counting Phase: Initialize
totalCountto 0 and acurrentNodetoroot.
- Counting Phase: Initialize
-
- Iterate through each character
cof the strings.
- Iterate through each character
-
- Move
currentNodeto its child corresponding toc.
- Move
-
- If the child node is
nullat any point, it means no more prefixes ofscan be found, so break the loop.
- If the child node is
-
- Add the
countof thecurrentNodetototalCount. Thiscountrepresents the number of words that are identical to the current prefix ofs.
- Add the
-
- After iterating through
s, returntotalCount.
- After iterating through
Walkthrough
A Trie is a specialized tree data structure for storing strings, where each node represents a character and paths from the root represent prefixes. First, we define a TrieNode class. Each node contains an array of children (one for each letter of the alphabet) and a counter, say count, to store how many words from the input array end at this node. We build the Trie by iterating through each word in the words array and inserting it. The insertion process involves traversing the Trie from the root, creating new nodes as necessary for each character in the word. When we reach the end of a word, we increment the count at the final node. After building the Trie, we traverse the string s character by character, starting from the Trie's root. For each prefix of s (i.e., s.substring(0, i+1)), we check the corresponding node in the Trie. The count at this node tells us how many words in the original array are exactly equal to this prefix. We sum up these counts for all prefixes of s to get the total.
class TrieNode { TrieNode[] children; int count; // Number of words ending at this node public TrieNode() { children = new TrieNode[26]; count = 0; }} class Solution { public int countPrefixes(String[] words, String s) { TrieNode root = new TrieNode(); // Build the Trie for (String word : words) { TrieNode curr = root; for (char c : word.toCharArray()) { if (curr.children[c - 'a'] == null) { curr.children[c - 'a'] = new TrieNode(); } curr = curr.children[c - 'a']; } curr.count++; } int totalPrefixes = 0; TrieNode curr = root; // Traverse s on the Trie and count prefixes for (char c : s.toCharArray()) { if (curr.children[c - 'a'] == null) { break; // No more prefixes of s exist in the Trie } curr = curr.children[c - 'a']; totalPrefixes += curr.count; // Add all words that are equal to the current prefix of s } return totalPrefixes; }}Complexity
Time
O(W + M), where `W` is the total number of characters in all words in the `words` array, and `M` is the length of `s`. Building the Trie takes `O(W)` time. Traversing `s` on the Trie takes `O(M)` time.
Space
O(W), where `W` is the total number of characters in all words in the `words` array. In the worst case, the Trie will have a distinct node for each character of each word (if there are no shared prefixes).
Trade-offs
Pros
Very efficient for problems with many prefix-based queries on the same set of words, as the build time is a one-time cost.
Avoids redundant comparisons by structuring the words in a prefix-based hierarchy.
Cons
Higher implementation complexity compared to the brute-force approach.
Consumes more memory due to the Trie data structure. For this specific problem with its small constraints and a single query, the space overhead might not be justified.
Solutions
Solution
public class Solution { public int CountPrefixes(string[] words, string s) { return words.Count(w => s.StartsWith(w)); }}class Solution { public int countPrefixes ( String [] words , String s ) { int ans = 0 ; for ( String w : words ) { if ( s . startsWith ( w )) { ++ ans ; } } return ans ; } }class Solution { public: int countPrefixes ( vector < string >& words , string s ) { int ans = 0 ; for ( auto & w : words ) { ans += s . starts_with ( w ); } return ans ; } };class Solution : def countPrefixes ( self , words : List [ str ], s : str ) -> int : return sum ( s . startswith ( w ) for w in words )Video 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.