LeetCode 2744 - Find Maximum Number of String Pairs
This problem gives us an array words containing distinct strings. Every string has length exactly 2, and all strings consist of lowercase English letters.
Difficulty: 🟢 Easy
Topics: Array, Hash Table, String, Simulation
Solution
LeetCode 2744 - Find Maximum Number of String Pairs
Problem Understanding
This problem gives us an array words containing distinct strings. Every string has length exactly 2, and all strings consist of lowercase English letters.
We can form a pair between two strings words[i] and words[j] when:
i < j- One string is exactly the reverse of the other
For example, "ab" and "ba" form a valid pair because reversing "ab" produces "ba".
Our goal is to determine the maximum number of such pairs that can be formed. Each string may participate in at most one pair.
Since all strings are distinct, a string can never appear twice in the array. This guarantee simplifies the problem significantly because if a reverse string exists, there is exactly one possible partner for that string.
The input represents a collection of two-character strings, and the output is the total number of disjoint reverse-string pairs that can be formed.
The constraints are very small:
1 <= words.length <= 50- Every string has length
2 - All strings are distinct
A brute-force solution would already be fast enough because there are at most 50 strings. However, there is a cleaner and more efficient hash set approach that directly exploits the reverse-string relationship.
Some important edge cases include:
- No reverse pairs exist at all.
- Multiple independent pairs exist.
- Strings such as
"aa"reverse to themselves. Since all strings are distinct, there can be only one"aa", so it can never form a pair. - The reverse string may appear before or after the current string in the array.
- The array may contain only one string.
Approaches
Brute Force
The most straightforward approach is to examine every possible pair of indices (i, j) where i < j.
For each pair, reverse one string and compare it with the other. If they match, we have found a valid pair and increase the answer.
Because every string is distinct, each valid reverse relationship corresponds to exactly one pair, so counting such pairs produces the correct answer.
This approach is correct because it explicitly checks every possible pair. However, it performs unnecessary comparisons and does not take advantage of the fact that we can quickly look up whether a reversed string exists.
Optimal Hash Set Approach
The key observation is that for any word, we only care whether its reversed version has already been seen.
As we iterate through the array:
- Compute the reversed version of the current word.
- If the reversed string already exists in a hash set, we have discovered a valid pair.
- Otherwise, store the current word in the set for future lookups.
Because every string is distinct, each pair is counted exactly once, specifically when the second member of the pair is encountered.
Hash set lookups are constant time on average, giving a very efficient solution.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Check every pair of strings |
| Optimal | O(n) | O(n) | Use a hash set to detect reversed strings |
Algorithm Walkthrough
- Create an empty hash set called
seen. - Create a variable
pairsand initialize it to0. - Iterate through each word in
words. - Reverse the current word. Since every word has length
2, reversing is very cheap. - Check whether the reversed string already exists in
seen. - If it exists, increment
pairsbecause we have found a valid reverse-string pair. The reverse must have appeared earlier in the array. - Add the current word to
seenso future words can match against it. - Continue until all words have been processed.
- Return
pairs.
Why it works
The algorithm maintains the invariant that seen contains exactly the words processed so far.
When processing a word w, if its reverse rev(w) is already in seen, then we have found the unique valid partner for w. Since all strings are distinct, this pair can only be discovered once, when the later word in the pair is processed. Therefore every valid pair is counted exactly once, and no invalid pair is counted.
Python Solution
from typing import List
class Solution:
def maximumNumberOfStringPairs(self, words: List[str]) -> int:
seen = set()
pairs = 0
for word in words:
if word[::-1] in seen:
pairs += 1
seen.add(word)
return pairs
The implementation follows the algorithm directly.
A hash set named seen stores all previously processed words. For each word, the expression word[::-1] creates its reversed version. If that reversed string already exists in the set, a valid pair has been found, so the answer is increased.
After the lookup, the current word is inserted into the set. This ensures that future words can match against it. Once all words have been processed, the accumulated pair count is returned.
Go Solution
func maximumNumberOfStringPairs(words []string) int {
seen := make(map[string]bool)
pairs := 0
for _, word := range words {
reversed := string([]byte{word[1], word[0]})
if seen[reversed] {
pairs++
}
seen[word] = true
}
return pairs
}
The Go implementation uses a map[string]bool as a hash set.
Since every string has length exactly 2, reversing a string is extremely simple. We construct a new string using the second character followed by the first character. The remainder of the algorithm is identical to the Python version.
There are no concerns about integer overflow because the maximum possible answer is at most 25, given that there are at most 50 strings.
Worked Examples
Example 1
Input:
["cd","ac","dc","ca","zz"]
| Step | Current Word | Reverse | Seen Before Processing | Pair Found? | Pairs |
|---|---|---|---|---|---|
| 1 | cd | dc | {} | No | 0 |
| 2 | ac | ca | {cd} | No | 0 |
| 3 | dc | cd | {cd, ac} | Yes | 1 |
| 4 | ca | ac | {cd, ac, dc} | Yes | 2 |
| 5 | zz | zz | {cd, ac, dc, ca} | No | 2 |
Final answer:
2
Example 2
Input:
["ab","ba","cc"]
| Step | Current Word | Reverse | Seen Before Processing | Pair Found? | Pairs |
|---|---|---|---|---|---|
| 1 | ab | ba | {} | No | 0 |
| 2 | ba | ab | {ab} | Yes | 1 |
| 3 | cc | cc | {ab, ba} | No | 1 |
Final answer:
1
Example 3
Input:
["aa","ab"]
| Step | Current Word | Reverse | Seen Before Processing | Pair Found? | Pairs |
|---|---|---|---|---|---|
| 1 | aa | aa | {} | No | 0 |
| 2 | ab | ba | {aa} | No | 0 |
Final answer:
0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | One pass through the array, constant-time hash lookups |
| Space | O(n) | The hash set may store all words |
The algorithm processes each word exactly once. Each iteration performs a constant amount of work: reversing a two-character string, checking membership in a hash set, and inserting into the hash set. Therefore the total running time is linear in the number of words.
The additional memory comes from storing previously seen words. In the worst case, all words are stored, giving linear space complexity.
Test Cases
sol = Solution()
assert sol.maximumNumberOfStringPairs(["cd", "ac", "dc", "ca", "zz"]) == 2 # example 1
assert sol.maximumNumberOfStringPairs(["ab", "ba", "cc"]) == 1 # example 2
assert sol.maximumNumberOfStringPairs(["aa", "ab"]) == 0 # example 3
assert sol.maximumNumberOfStringPairs(["ab"]) == 0 # single element
assert sol.maximumNumberOfStringPairs(["ab", "ba"]) == 1 # one pair
assert sol.maximumNumberOfStringPairs(["ab", "cd"]) == 0 # no pairs
assert sol.maximumNumberOfStringPairs(["ab", "ba", "cd", "dc"]) == 2 # multiple pairs
assert sol.maximumNumberOfStringPairs(["ab", "cd", "dc", "ba"]) == 2 # pairs found later
assert sol.maximumNumberOfStringPairs(["aa"]) == 0 # self-reversing string alone
assert sol.maximumNumberOfStringPairs(["aa", "bb", "cc"]) == 0 # all self-reversing
assert sol.maximumNumberOfStringPairs(
["ab", "ba", "cd", "dc", "ef", "fe", "gh", "hg"]
) == 4 # many independent pairs
assert sol.maximumNumberOfStringPairs(
["ab", "bc", "cd", "de", "ef"]
) == 0 # larger no-pair case
Test Summary
| Test | Why |
|---|---|
["cd","ac","dc","ca","zz"] |
Validates multiple pairs |
["ab","ba","cc"] |
Validates a single pair |
["aa","ab"] |
Validates no pair formation |
["ab"] |
Minimum size input |
["ab","ba"] |
Simplest successful pair |
["ab","cd"] |
No matching reverses |
["ab","ba","cd","dc"] |
Multiple independent pairs |
["ab","cd","dc","ba"] |
Reverse strings appear later |
["aa"] |
Self-reversing string alone |
["aa","bb","cc"] |
Multiple self-reversing strings |
["ab","ba","cd","dc","ef","fe","gh","hg"] |
Larger number of pairs |
["ab","bc","cd","de","ef"] |
Larger input with zero matches |
Edge Cases
Self-Reversing Strings
Strings such as "aa", "bb", or "cc" are equal to their own reverse. A common mistake is to assume they can form pairs with themselves. Since all strings in the array are distinct, such a string appears at most once. Therefore it can never form a valid pair. The hash-set solution handles this naturally because no identical string will appear later.
Single Element Array
When the array contains only one string, no pair can possibly be formed because a pair requires two different indices. The algorithm processes the lone string, finds no reverse in the set, and correctly returns 0.
Reverse Appears Earlier or Later
A valid pair may appear in either order within the array. For example, "ab" may appear before "ba", or "ba" may appear before "ab". The algorithm works in both situations because it always checks whether the reverse has already been seen. Exactly one of the two strings will encounter its partner in the set, ensuring the pair is counted once.
Multiple Independent Pairs
The array may contain several unrelated reverse-string pairs. Because every valid pair involves distinct strings and the input contains no duplicates, each pair is discovered independently. The hash set stores all previously seen words, allowing every valid pair to be found without interference from the others.