LeetCode 3042 - Count Prefix and Suffix Pairs I
The problem gives us an array of strings called words. For every pair of indices (i, j) where i < j, we must determine whether words[i] is both a prefix and a suffix of words[j]. A string is a prefix of another string if it appears at the beginning.
Difficulty: 🟢 Easy
Topics: Array, String, Trie, Rolling Hash, String Matching, Hash Function
Solution
LeetCode 3042 - Count Prefix and Suffix Pairs I
Problem Understanding
The problem gives us an array of strings called words. For every pair of indices (i, j) where i < j, we must determine whether words[i] is both a prefix and a suffix of words[j].
A string is a prefix of another string if it appears at the beginning. A string is a suffix if it appears at the end.
For example:
"aba"is a prefix of"ababa"because"ababa"starts with"aba"."aba"is also a suffix of"ababa"because"ababa"ends with"aba".- Therefore,
"aba"satisfies both conditions for"ababa".
Our task is to count how many index pairs satisfy this property.
The input consists of:
- An array
words - Each element is a lowercase English string
- We must consider every pair
(i, j)such thati < j
The output is a single integer representing the number of valid pairs.
The constraints are very small:
1 <= words.length <= 501 <= words[i].length <= 10
These constraints are important because they tell us that even a straightforward comparison of every pair of strings is completely feasible. There are at most:
50 × 49 / 2 = 1225pairs- Each string has length at most
10
This means a simple solution will easily run within limits.
Some important edge cases include:
- Strings that are exactly equal, since a string is always both a prefix and suffix of itself.
- Single-character strings.
- Cases where the candidate prefix is longer than the target string.
- Arrays containing only one word, which produce zero valid pairs.
- Repeated strings appearing multiple times at different indices.
Approaches
Brute Force Approach
The most direct solution is to examine every pair (i, j) where i < j.
For each pair:
- Let
a = words[i]. - Let
b = words[j]. - Check whether
bstarts witha. - Check whether
bends witha. - If both conditions are true, increment the answer.
This approach is correct because the problem definition directly asks us to evaluate every valid index pair. By checking all pairs and counting exactly those that satisfy the condition, we obtain the correct result.
The time complexity is acceptable because there are at most 1225 pairs and each string comparison involves at most 10 characters.
Optimal Approach
Because the constraints are extremely small, there is no need for a more sophisticated structure such as a Trie, rolling hash, or advanced string matching algorithm.
The key observation is that Python and Go already provide efficient operations for checking prefixes and suffixes. Since string lengths are at most 10, each check is effectively constant time.
Therefore, the optimal solution is simply:
- Enumerate all pairs
(i, j) - Use prefix and suffix checks
- Count the valid pairs
This solution is both simple and optimal for the given constraints.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n² · m) | O(1) | Check every pair and compare characters manually |
| Optimal | O(n² · m) | O(1) | Check every pair using built-in prefix/suffix operations |
Here:
nis the number of words.mis the maximum word length.
Since m ≤ 10, the practical runtime is extremely small.
Algorithm Walkthrough
- Initialize a variable
countto0. - Iterate through all possible first indices
ifrom0ton - 1. - For each
i, iterate through all indicesjsuch thatj > i. - Let:
prefixSuffix = words[i]target = words[j]
- Check whether
targetstarts withprefixSuffix. - Check whether
targetends withprefixSuffix. - If both checks return true, increment
count. - After all pairs have been processed, return
count.
Why it works
The algorithm explicitly evaluates every pair (i, j) with i < j, which are exactly the pairs defined in the problem statement. For each pair, it verifies the required property that words[i] is both a prefix and a suffix of words[j]. Since every valid pair is counted exactly once and every invalid pair is ignored, the final count is correct.
Python Solution
from typing import List
class Solution:
def countPrefixSuffixPairs(self, words: List[str]) -> int:
count = 0
n = len(words)
for i in range(n):
for j in range(i + 1, n):
if words[j].startswith(words[i]) and words[j].endswith(words[i]):
count += 1
return count
The implementation follows the algorithm directly.
The variable count stores the number of valid pairs found so far. The nested loops generate every pair (i, j) with i < j. For each pair, the built-in methods startswith() and endswith() determine whether words[i] is simultaneously a prefix and suffix of words[j].
Whenever both conditions are satisfied, the answer is incremented. After all pairs are processed, the result is returned.
Go Solution
func countPrefixSuffixPairs(words []string) int {
count := 0
n := len(words)
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
if len(words[i]) <= len(words[j]) &&
words[j][:len(words[i])] == words[i] &&
words[j][len(words[j])-len(words[i]):] == words[i] {
count++
}
}
}
return count
}
The Go version performs the same logic. Since Go does not provide built-in StartsWith and EndsWith methods on strings directly, we compare slices of the string.
Before slicing, we verify that words[i] is not longer than words[j]. This prevents invalid slice operations and correctly handles cases where a longer string cannot possibly be a prefix or suffix of a shorter one.
Worked Examples
Example 1
words = ["a","aba","ababa","aa"]
Initial state:
| Pair | Prefix/Suffix Candidate | Target | Valid? |
|---|---|---|---|
| (0,1) | "a" | "aba" | Yes |
| (0,2) | "a" | "ababa" | Yes |
| (0,3) | "a" | "aa" | Yes |
| (1,2) | "aba" | "ababa" | Yes |
| (1,3) | "aba" | "aa" | No |
| (2,3) | "ababa" | "aa" | No |
Running count:
| Pair Processed | Count |
|---|---|
| (0,1) | 1 |
| (0,2) | 2 |
| (0,3) | 3 |
| (1,2) | 4 |
| (1,3) | 4 |
| (2,3) | 4 |
Final answer:
4
Example 2
words = ["pa","papa","ma","mama"]
| Pair | Candidate | Target | Valid? |
|---|---|---|---|
| (0,1) | "pa" | "papa" | Yes |
| (0,2) | "pa" | "ma" | No |
| (0,3) | "pa" | "mama" | No |
| (1,2) | "papa" | "ma" | No |
| (1,3) | "papa" | "mama" | No |
| (2,3) | "ma" | "mama" | Yes |
Final count:
2
Example 3
words = ["abab","ab"]
| Pair | Candidate | Target | Valid? |
|---|---|---|---|
| (0,1) | "abab" | "ab" | No |
The candidate string is longer than the target string, so it cannot be both a prefix and a suffix.
Final answer:
0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n² · m) | Every pair is checked, each check examines up to m characters |
| Space | O(1) | Only a few variables are used |
The nested loops generate all O(n²) pairs. For each pair, the prefix and suffix checks may inspect up to m characters. Since m ≤ 10, the runtime is very small in practice. No auxiliary data structures proportional to input size are used, so the space complexity remains constant.
Test Cases
from typing import List
s = Solution()
assert s.countPrefixSuffixPairs(["a", "aba", "ababa", "aa"]) == 4 # Example 1
assert s.countPrefixSuffixPairs(["pa", "papa", "ma", "mama"]) == 2 # Example 2
assert s.countPrefixSuffixPairs(["abab", "ab"]) == 0 # Example 3
assert s.countPrefixSuffixPairs(["a"]) == 0 # Single element
assert s.countPrefixSuffixPairs(["a", "a"]) == 1 # Equal strings
assert s.countPrefixSuffixPairs(["abc", "abc"]) == 1 # Exact match
assert s.countPrefixSuffixPairs(["ab", "abab"]) == 1 # Prefix and suffix
assert s.countPrefixSuffixPairs(["ab", "abcd"]) == 0 # Prefix only
assert s.countPrefixSuffixPairs(["cd", "abcd"]) == 0 # Suffix only
assert s.countPrefixSuffixPairs(["x", "xx", "xxx"]) == 3 # Multiple valid pairs
assert s.countPrefixSuffixPairs(["abc", "def", "ghi"]) == 0 # No matches
assert s.countPrefixSuffixPairs(["aa", "aaaa", "aaaaaa"]) == 3 # Repeated pattern
assert s.countPrefixSuffixPairs(["a", "b", "a"]) == 0 # Ordering matters
assert s.countPrefixSuffixPairs(["z", "zz", "zzz", "zzzz"]) == 6 # Every pair valid
Test Summary
| Test | Why |
|---|---|
["a","aba","ababa","aa"] |
Official example |
["pa","papa","ma","mama"] |
Official example |
["abab","ab"] |
Candidate longer than target |
["a"] |
Minimum array size |
["a","a"] |
Equal strings |
["abc","abc"] |
Full string match |
["ab","abab"] |
Valid prefix and suffix |
["ab","abcd"] |
Prefix only |
["cd","abcd"] |
Suffix only |
["x","xx","xxx"] |
Multiple overlapping matches |
["abc","def","ghi"] |
No valid pairs |
["aa","aaaa","aaaaaa"] |
Repeated pattern strings |
["a","b","a"] |
Verifies index ordering |
["z","zz","zzz","zzzz"] |
Dense valid-pair scenario |
Edge Cases
A Candidate String Longer Than the Target
A string cannot be a prefix or suffix of a shorter string if its length exceeds the target's length. For example, "abab" cannot be a prefix or suffix of "ab". This can easily cause bugs in implementations that perform slicing without checking lengths first. The Go solution explicitly verifies the length condition before slicing, while Python's built-in methods naturally return False.
Identical Strings
If two strings are exactly the same, then one is both a prefix and a suffix of the other. For example, "abc" and "abc" form a valid pair. Some implementations mistakenly require the second string to be strictly longer. The presented solution correctly counts identical strings because both prefix and suffix checks succeed.
Single-Element Arrays
When words contains only one string, there are no pairs (i, j) satisfying i < j. The answer must therefore be 0. The nested loops naturally handle this case because the inner loop never executes.
Repeated Characters and Overlapping Patterns
Strings such as "a", "aa", "aaa", and "aaaa" create many valid prefix-suffix relationships. These cases often expose logic errors where only prefixes or only suffixes are checked. The implementation independently verifies both conditions, ensuring that overlapping and repetitive patterns are counted correctly.
Prefix Without Suffix, or Suffix Without Prefix
A common mistake is checking only one side. For example:
"ab"is a prefix of"abcd"but not a suffix."cd"is a suffix of"abcd"but not a prefix.
The solution requires both conditions to be true simultaneously, which exactly matches the problem definition.