3042. Count Prefix and Suffix Pairs I
Problem Description
You are given a 0-indexed array of strings called words
.
The problem asks you to find pairs of strings where one string appears as both the beginning (prefix) and the ending (suffix) of another string.
Specifically, you need to:
- Consider all pairs of indices
(i, j)
wherei < j
- Check if
words[i]
is both a prefix AND a suffix ofwords[j]
- Count how many such pairs exist
For a string to be both a prefix and suffix of another string, it must:
- Start at the beginning of the target string (prefix condition)
- End at the end of the target string (suffix condition)
For example:
"aba"
is both a prefix and suffix of"ababa"
because:"ababa"
starts with"aba"
(prefix β)"ababa"
ends with"aba"
(suffix β)
"abc"
is NOT both a prefix and suffix of"abcd"
because:"abcd"
starts with"abc"
(prefix β)"abcd"
does NOT end with"abc"
(suffix β)
The solution uses a nested loop to check all valid pairs. For each word at index i
, it checks all words at indices greater than i
. It uses Python's startswith()
and endswith()
methods to verify if words[i]
is both a prefix and suffix of words[j]
. The expression t.endswith(s) and t.startswith(s)
returns True
(which equals 1) when both conditions are met, and False
(which equals 0) otherwise, allowing direct addition to the answer counter.
Intuition
The key insight is that we need to check every possible pair where one string could potentially be both a prefix and suffix of another. Since we're looking for pairs (i, j)
where i < j
, we need to compare each string with all strings that come after it in the array.
The most straightforward approach is to check each pair directly. For any two strings, we can determine if one is both a prefix and suffix of the other by using built-in string operations. A string s
is both a prefix and suffix of string t
if and only if t
starts with s
AND t
ends with s
.
Why does this work? Consider that if a string appears at both the beginning and end of another string, these two occurrences might overlap (like "aba"
in "ababa"
), or they might be the same occurrence if the smaller string equals the larger string entirely. The beauty of using startswith()
and endswith()
is that they handle all these cases naturally without us having to worry about the overlap logic.
The brute force nature of checking all pairs is acceptable here because:
- We must examine every valid pair at least once to determine if it satisfies our condition
- The check for each pair (prefix and suffix verification) can be done efficiently using built-in string methods
- There's no apparent pattern or property that would allow us to skip checking certain pairs without potentially missing valid answers
The solution leverages Python's boolean-to-integer conversion where True
equals 1
and False
equals 0
, allowing us to directly add the result of our condition check to the running count.
Learn more about Trie patterns.
Solution Approach
The solution uses a nested loop enumeration approach to check all valid index pairs.
Implementation Steps:
-
Initialize Counter: Start with
ans = 0
to keep track of valid pairs. -
Outer Loop with Enumeration: Use
enumerate(words)
to iterate through each word along with its indexi
. This gives us the first element of each potential pair(i, j)
. -
Inner Loop for Subsequent Elements: For each word at index
i
, iterate through all words that come after it using slice notationwords[i + 1:]
. This ensures we only check pairs wherei < j
. -
Condition Check: For each pair
(words[i], words[j])
, check ifwords[i]
is both a prefix and suffix ofwords[j]
:- Use
t.startswith(s)
to check ifs
is a prefix oft
- Use
t.endswith(s)
to check ifs
is a suffix oft
- Combine both conditions with
and
operator
- Use
-
Count Valid Pairs: The expression
t.endswith(s) and t.startswith(s)
evaluates to eitherTrue
(1) orFalse
(0). Adding this directly toans
increments the counter when a valid pair is found.
Time Complexity: O(nΒ² Γ m)
where n
is the number of words and m
is the average length of the strings. We check n Γ (n-1) / 2
pairs, and each check takes O(m)
time for the prefix/suffix comparison.
Space Complexity: O(1)
as we only use a constant amount of extra space for the counter variable. The slice words[i + 1:]
creates a view in Python 3, not a copy.
The elegance of this solution lies in its simplicity - it directly implements the problem's requirements without unnecessary complexity, using Python's built-in string methods that are optimized for these exact operations.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with words = ["a", "aba", "ababa", "aa"]
.
Step 1: Initialize counter
ans = 0
Step 2: Process i=0, s="a"
- Check against remaining words:
- j=1: Is "a" both prefix and suffix of "aba"?
- "aba".startswith("a") = True β
- "aba".endswith("a") = True β
- Both conditions met β ans = 0 + 1 = 1
- j=2: Is "a" both prefix and suffix of "ababa"?
- "ababa".startswith("a") = True β
- "ababa".endswith("a") = True β
- Both conditions met β ans = 1 + 1 = 2
- j=3: Is "a" both prefix and suffix of "aa"?
- "aa".startswith("a") = True β
- "aa".endswith("a") = True β
- Both conditions met β ans = 2 + 1 = 3
- j=1: Is "a" both prefix and suffix of "aba"?
Step 3: Process i=1, s="aba"
- Check against remaining words:
- j=2: Is "aba" both prefix and suffix of "ababa"?
- "ababa".startswith("aba") = True β
- "ababa".endswith("aba") = True β
- Both conditions met β ans = 3 + 1 = 4
- j=3: Is "aba" both prefix and suffix of "aa"?
- "aa".startswith("aba") = False β
- "aa".endswith("aba") = False β
- Conditions not met β ans stays 4
- j=2: Is "aba" both prefix and suffix of "ababa"?
Step 4: Process i=2, s="ababa"
- Check against remaining words:
- j=3: Is "ababa" both prefix and suffix of "aa"?
- "aa".startswith("ababa") = False β
- "aa".endswith("ababa") = False β
- Conditions not met β ans stays 4
- j=3: Is "ababa" both prefix and suffix of "aa"?
Step 5: Process i=3, s="aa"
- No remaining words to check (i=3 is the last index)
Final Result: ans = 4
The valid pairs found were: (0,1), (0,2), (0,3), and (1,2), giving us a total count of 4.
Solution Implementation
1class Solution:
2 def countPrefixSuffixPairs(self, words: List[str]) -> int:
3 """
4 Count pairs (i, j) where i < j and words[i] is both a prefix and suffix of words[j].
5
6 Args:
7 words: List of strings to check for prefix-suffix pairs
8
9 Returns:
10 Number of valid prefix-suffix pairs
11 """
12 count = 0
13
14 # Iterate through each word with its index
15 for i, current_word in enumerate(words):
16 # Check all words that come after the current word
17 for next_word in words[i + 1:]:
18 # Check if current_word is both prefix and suffix of next_word
19 # The expression evaluates to True (1) if both conditions are met, False (0) otherwise
20 is_prefix = next_word.startswith(current_word)
21 is_suffix = next_word.endswith(current_word)
22 count += (is_prefix and is_suffix)
23
24 return count
25
1class Solution {
2 /**
3 * Counts the number of prefix-suffix pairs in the given array of words.
4 * A pair (i, j) is valid if i < j and words[i] is both a prefix and suffix of words[j].
5 *
6 * @param words Array of strings to check for prefix-suffix pairs
7 * @return The total count of valid prefix-suffix pairs
8 */
9 public int countPrefixSuffixPairs(String[] words) {
10 int pairCount = 0;
11 int wordCount = words.length;
12
13 // Iterate through each word as a potential prefix/suffix
14 for (int i = 0; i < wordCount; ++i) {
15 String currentWord = words[i];
16
17 // Check all words that come after the current word
18 for (int j = i + 1; j < wordCount; ++j) {
19 String targetWord = words[j];
20
21 // Check if currentWord is both prefix and suffix of targetWord
22 if (targetWord.startsWith(currentWord) && targetWord.endsWith(currentWord)) {
23 ++pairCount;
24 }
25 }
26 }
27
28 return pairCount;
29 }
30}
31
1class Solution {
2public:
3 int countPrefixSuffixPairs(vector<string>& words) {
4 int count = 0;
5 int wordsSize = words.size();
6
7 // Iterate through each word as a potential prefix/suffix
8 for (int i = 0; i < wordsSize; ++i) {
9 string currentWord = words[i];
10
11 // Check all words that come after the current word
12 for (int j = i + 1; j < wordsSize; ++j) {
13 string targetWord = words[j];
14
15 // Check if currentWord is both a prefix and suffix of targetWord
16 // find(currentWord) == 0 checks if currentWord is a prefix (starts at position 0)
17 // rfind(currentWord) == targetWord.length() - currentWord.length() checks if currentWord is a suffix
18 if (targetWord.find(currentWord) == 0 &&
19 targetWord.rfind(currentWord) == targetWord.length() - currentWord.length()) {
20 ++count;
21 }
22 }
23 }
24
25 return count;
26 }
27};
28
1/**
2 * Counts the number of prefix-suffix pairs in the given array of words.
3 * A pair (i, j) is counted if words[i] is both a prefix and suffix of words[j] where i < j.
4 *
5 * @param words - Array of strings to check for prefix-suffix pairs
6 * @returns The total count of valid prefix-suffix pairs
7 */
8function countPrefixSuffixPairs(words: string[]): number {
9 // Initialize counter for valid pairs
10 let count: number = 0;
11
12 // Iterate through each word as a potential prefix/suffix
13 for (let i = 0; i < words.length; i++) {
14 const currentWord: string = words[i];
15
16 // Check all words that come after the current word
17 for (let j = i + 1; j < words.length; j++) {
18 const targetWord: string = words[j];
19
20 // Check if currentWord is both prefix and suffix of targetWord
21 if (targetWord.startsWith(currentWord) && targetWord.endsWith(currentWord)) {
22 count++;
23 }
24 }
25 }
26
27 return count;
28}
29
Time and Space Complexity
Time Complexity: O(n^2 Γ m)
The algorithm uses two nested loops:
- The outer loop iterates through each word in the list, running
n
times wheren
is the length ofwords
- The inner loop iterates through all words after the current index, which in the worst case runs
n-1
,n-2
, ...,1
times respectively - This gives us
n Γ (n-1) / 2
iterations total, which isO(n^2)
- For each pair of words
(s, t)
, we perform two string operations:t.endswith(s)
: This takesO(m)
time wherem
is the maximum length of the stringst.startswith(s)
: This also takesO(m)
time
- Therefore, the overall time complexity is
O(n^2 Γ m)
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space:
- The variable
ans
to store the count - The loop variables
i
,s
, andt
which are references to existing strings - No additional data structures are created
- The slicing operation
words[i + 1:]
creates a view/iterator that doesn't copy the actual strings
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Overlooking the Special Case Where Prefix Equals the Entire String
A common misconception is thinking that a string cannot be both a prefix and suffix of another string if they have the same length. However, when words[i]
equals words[j]
, it is technically both a prefix and suffix of itself.
Example:
- If
words = ["abc", "abc"]
, then"abc"
is both a prefix and suffix of"abc"
at index 1 - This should count as a valid pair (0, 1)
Solution: The current implementation handles this correctly since startswith()
and endswith()
return True
when the strings are identical. No special handling needed.
2. Incorrect Index Handling in Inner Loop
A frequent mistake is using range(i, len(words))
or range(i+1, len(words))
with indexing like words[j]
but forgetting to properly reference the index j
.
Incorrect Implementation:
for i in range(len(words)):
for j in range(i + 1, len(words)):
# Forgot to use words[j], used words[i+1:] instead
for t in words[i + 1:]: # Wrong! This creates nested iteration
count += t.startswith(words[i]) and t.endswith(words[i])
Solution: Either use index-based iteration consistently or use the slice approach as shown in the correct solution. The slice words[i + 1:]
automatically handles the "all words after index i" requirement.
3. Performance Degradation with Repeated String Operations
Some developers might try to optimize by caching or pre-processing, but this can actually hurt performance for this problem:
Inefficient Attempt:
# Trying to be "clever" by pre-computing all prefixes/suffixes
for i, s in enumerate(words):
prefixes = [words[j][:len(s)] for j in range(i+1, len(words))]
suffixes = [words[j][-len(s):] for j in range(i+1, len(words))]
# This creates unnecessary string slices and lists
Solution: Stick with the built-in startswith()
and endswith()
methods. They're optimized at the C level and avoid creating intermediate strings. They also handle edge cases like when the prefix/suffix is longer than the target string.
4. Misunderstanding the Direction of Comparison
A critical error is checking if words[j]
is a prefix/suffix of words[i]
instead of the reverse.
Wrong Direction:
for i, s in enumerate(words):
for t in words[i + 1:]:
# Wrong! Checking if t is prefix/suffix of s
count += s.startswith(t) and s.endswith(t)
Solution: Remember that we're checking if words[i]
(the earlier word) is a prefix/suffix of words[j]
(the later word). The correct check is words[j].startswith(words[i])
.
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
Trie Introduction Imagine you have a magic dictionary where as soon as you start spelling a word it immediately tells you how many words that start with those letters How cool would that be This is exactly the kind of magic a data structure called trie performs What's a Trie
Coding Interview 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
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 assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!