Sum of Prefix Scores of Strings
HardPrompt
You are given an array words of size n consisting of non-empty strings.
We define the score of a string term as the number of strings words[i] such that term is a prefix of words[i].
- For example, if
words = ["a", "ab", "abc", "cab"], then the score of"ab"is2, since"ab"is a prefix of both"ab"and"abc".
Return an array answer of size n where answer[i] is the sum of scores of every non-empty prefix of words[i].
Note that a string is considered as a prefix of itself.
Example 1:
Input: words = ["abc","ab","bc","b"]
Output: [5,4,3,2]
Explanation: The answer for each string is the following:
- "abc" has 3 prefixes: "a", "ab", and "abc".
- There are 2 strings with the prefix "a", 2 strings with the prefix "ab", and 1 string with the prefix "abc".
The total is answer[0] = 2 + 2 + 1 = 5.
- "ab" has 2 prefixes: "a" and "ab".
- There are 2 strings with the prefix "a", and 2 strings with the prefix "ab".
The total is answer[1] = 2 + 2 = 4.
- "bc" has 2 prefixes: "b" and "bc".
- There are 2 strings with the prefix "b", and 1 string with the prefix "bc".
The total is answer[2] = 2 + 1 = 3.
- "b" has 1 prefix: "b".
- There are 2 strings with the prefix "b".
The total is answer[3] = 2.Example 2:
Input: words = ["abcd"]
Output: [4]
Explanation:
"abcd" has 4 prefixes: "a", "ab", "abc", and "abcd".
Each prefix has a score of one, so the total is answer[0] = 1 + 1 + 1 + 1 = 4.
Constraints:
1 <= words.length <= 10001 <= words[i].length <= 1000words[i]consists of lowercase English letters.
Approaches
3 approaches with complexity analysis and trade-offs.
The most efficient solution utilizes a Trie data structure, which is perfectly suited for problems involving prefixes. We first build a Trie from all the words, incrementing a counter in each node to track how many words pass through it (i.e., the prefix score). Then, for each word, we traverse the Trie and sum the counters of the nodes corresponding to its prefixes.
Algorithm
- Define a
TrieNodeclass containing an array of children (e.g., size 26 for lowercase English letters) and an integercount. - First Pass (Build Trie):
- Create a
rootnode for the Trie. - Iterate through each
wordin thewordsarray. - For each
word, traverse the Trie from theroot, character by character. - For each character, move to the corresponding child node. If it doesn't exist, create it.
- Increment the
countof the visited node. Thiscountrepresents the score of the prefix ending at that node.
- Create a
- Second Pass (Calculate Scores):
- Initialize an integer array
answerof sizen. - Iterate through each
wordat indexiin thewordsarray. - Initialize
totalScoreto 0. - Traverse the Trie again for the current
word, character by character. - At each node in the traversal, add its
counttototalScore. - After traversing the entire word, store
totalScoreinanswer[i].
- Initialize an integer array
- Return the
answerarray.
Walkthrough
A Trie, or prefix tree, is an ideal data structure for this problem. Each node in the Trie can represent a character, and a path from the root to a node represents a prefix. We can augment the Trie node to store a count of how many words in the input array share the prefix represented by that node.
The algorithm works in two passes:
- Build Trie and Count Prefixes: We insert every word from the input array into the Trie. While inserting a word, we traverse the Trie character by character. For each node we visit (or create), we increment its
countfield. By the end of this pass, thecountat any node will represent the score of the prefix corresponding to that node. - Calculate Sum of Scores: We iterate through the input
wordsarray again. For each word, we traverse the Trie following the path of its characters. As we visit each node along the path, we add itscountto a running total for the current word. This sum is the answer for that word.
This approach is highly efficient because it processes each character of each word a constant number of times.
class TrieNode { TrieNode[] children; int count; public TrieNode() { children = new TrieNode[26]; count = 0; }} class Solution { public int[] sumPrefixScores(String[] words) { TrieNode root = new TrieNode(); // Pass 1: Build the Trie and count prefixes for (String word : words) { TrieNode curr = root; for (char c : word.toCharArray()) { int index = c - 'a'; if (curr.children[index] == null) { curr.children[index] = new TrieNode(); } curr = curr.children[index]; curr.count++; } } int n = words.length; int[] answer = new int[n]; // Pass 2: Calculate the sum of scores for each word for (int i = 0; i < n; i++) { String word = words[i]; TrieNode curr = root; int totalScore = 0; for (char c : word.toCharArray()) { int index = c - 'a'; curr = curr.children[index]; totalScore += curr.count; } answer[i] = totalScore; } return answer; }}Complexity
Time
O(L), where `L` is the total number of characters in all words combined (`sum(|w|)`). We traverse each character of each word twice: once to build the Trie and once to calculate the scores. Each character operation (node creation, traversal, increment) takes constant time.
Space
O(L), where `L` is the total number of characters in all words combined (`sum(|w|)`). The space is dominated by the Trie. In the worst case, where there are no overlapping prefixes, the number of nodes in the Trie is equal to the total number of characters `L`. Each node has a constant size.
Trade-offs
Pros
Highly efficient in both time and space.
It's the optimal solution for this problem as it avoids string manipulations and redundant computations.
Cons
Requires knowledge of the Trie data structure, making it slightly more complex to implement than the other approaches.
Solutions
Solution
class Trie { private Trie [] children = new Trie [ 26 ]; private int cnt ; public void insert ( String w ) { Trie node = this ; for ( char c : w . toCharArray ()) { c -= 'a' ; if ( node . children [ c ] == null ) { node . children [ c ] = new Trie (); } node = node . children [ c ]; ++ node . cnt ; } } public int search ( String w ) { Trie node = this ; int ans = 0 ; for ( char c : w . toCharArray ()) { c -= 'a' ; if ( node . children [ c ] == null ) { return ans ; } node = node . children [ c ]; ans += node . cnt ; } return ans ; } } class Solution { public int [] sumPrefixScores ( String [] words ) { Trie trie = new Trie (); for ( String w : words ) { trie . insert ( w ); } int [] ans = new int [ words . length ]; for ( int i = 0 ; i < words . length ; ++ i ) { ans [ i ] = trie . search ( words [ i ]); } return ans ; } }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.