Facebook Pixel

3042. Count Prefix and Suffix Pairs I

EasyTrieArrayStringString MatchingHash FunctionRolling Hash
Leetcode Link

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:

  1. Consider all pairs of indices (i, j) where i < j
  2. Check if words[i] is both a prefix AND a suffix of words[j]
  3. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. We must examine every valid pair at least once to determine if it satisfies our condition
  2. The check for each pair (prefix and suffix verification) can be done efficiently using built-in string methods
  3. 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:

  1. Initialize Counter: Start with ans = 0 to keep track of valid pairs.

  2. Outer Loop with Enumeration: Use enumerate(words) to iterate through each word along with its index i. This gives us the first element of each potential pair (i, j).

  3. Inner Loop for Subsequent Elements: For each word at index i, iterate through all words that come after it using slice notation words[i + 1:]. This ensures we only check pairs where i < j.

  4. Condition Check: For each pair (words[i], words[j]), check if words[i] is both a prefix and suffix of words[j]:

    • Use t.startswith(s) to check if s is a prefix of t
    • Use t.endswith(s) to check if s is a suffix of t
    • Combine both conditions with and operator
  5. Count Valid Pairs: The expression t.endswith(s) and t.startswith(s) evaluates to either True (1) or False (0). Adding this directly to ans 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 Evaluator

Example 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

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

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

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 where n is the length of words
  • 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 is O(n^2)
  • For each pair of words (s, t), we perform two string operations:
    • t.endswith(s): This takes O(m) time where m is the maximum length of the strings
    • t.startswith(s): This also takes O(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, and t 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]).

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More