LeetCode 2131 - Longest Palindrome by Concatenating Two Letter Words
This problem asks us to construct the longest possible palindrome using a list of two-letter words. Each word can be used at most once, and the order of concatenation can be chosen freely.
Difficulty: 🟡 Medium
Topics: Array, Hash Table, String, Greedy, Counting
Solution
Problem Understanding
This problem asks us to construct the longest possible palindrome using a list of two-letter words. Each word can be used at most once, and the order of concatenation can be chosen freely. A palindrome reads the same forwards and backwards, which means that for a word pair to appear symmetrically around the center, one word must be the reverse of the other. Additionally, words that are already palindromes themselves (like "gg" or "cc") can appear in the middle or as mirrored pairs on either side.
The input is an array of strings words, where each string is exactly two lowercase letters. The output is the total length of the palindrome formed, which is simply the sum of lengths of the words used. Constraints indicate that the list can have up to 100,000 words, which rules out naive brute-force attempts like generating all permutations.
Important edge cases include:
- Words that are already palindromes (like "aa") can be used in the middle of the palindrome.
- Words that have no reverse counterpart in the list cannot be used symmetrically.
- Multiple identical palindromic words (like ["cc","cc","cc"]) may allow only one in the middle but can pair the others on sides.
Understanding these constraints and properties allows us to design an efficient solution using a hash map.
Approaches
Brute-force approach would involve generating all permutations of the words and checking each concatenation for palindrome validity. This is correct but infeasible because for n = 10^5 words, the number of permutations is astronomically large, making it impossible to compute in reasonable time.
The key insight is that a palindrome can be constructed by pairing words with their reversed counterparts. We only need to count the occurrences of each word and check if its reverse exists. For words that are palindromes themselves, we can use an even number of them to form mirrored pairs, and possibly place a single one in the middle if no other middle word exists.
This reduces the problem to hash table counting, which allows us to compute the longest palindrome in linear time with respect to the number of words.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n!) | O(n!) | Generate all permutations and check each for palindrome, infeasible for large n |
| Optimal | O(n) | O(n) | Use hash map to count words and pair each word with its reverse efficiently |
Algorithm Walkthrough
- Initialize a hash map
countto store the frequency of each word inwords. - Initialize a variable
lengthto keep track of the total palindrome length. - Initialize a boolean flag
used_middleto indicate whether a central palindromic word has been used. - Iterate over each unique word
win the hash map. - Check if
wis a palindrome itself (i.e.,w[0] == w[1]). If so:
- Add
2 * (count[w] // 2) * 2tolengthfor mirrored pairs (each word contributes 2 letters). - If there is an odd count of
wandused_middleisFalse, add 2 tolengthfor the central word and setused_middletoTrue.
- If
wis not a palindrome, compute its reverserev.
- If
revexists in the hash map, add2 * 2 * min(count[w], count[rev])tolength(each pair contributes 4 letters). - Mark both
wandrevas processed by setting their counts to zero to avoid double counting.
- After iterating all words, return
length.
Why it works: Every valid pair is counted exactly once, and a single palindromic word can serve as the center. This ensures that the resulting string is the longest possible palindrome.
Python Solution
from typing import List
from collections import Counter
class Solution:
def longestPalindrome(self, words: List[str]) -> int:
count = Counter(words)
length = 0
used_middle = False
for word in list(count.keys()):
if count[word] == 0:
continue
rev = word[::-1]
if word == rev:
pairs = count[word] // 2
length += pairs * 4
if count[word] % 2 == 1 and not used_middle:
length += 2
used_middle = True
count[word] = 0
elif rev in count:
pairs = min(count[word], count[rev])
length += pairs * 4
count[word] -= pairs
count[rev] -= pairs
return length
This code first counts the frequency of each word, then iterates through each word to find either mirrored pairs or palindromic words for the center. Each word is processed exactly once, ensuring correctness and efficiency.
Go Solution
func longestPalindrome(words []string) int {
count := make(map[string]int)
for _, word := range words {
count[word]++
}
length := 0
usedMiddle := false
for word, c := range count {
if c == 0 {
continue
}
rev := string([]byte{word[1], word[0]})
if word[0] == word[1] {
pairs := c / 2
length += pairs * 4
if c%2 == 1 && !usedMiddle {
length += 2
usedMiddle = true
}
count[word] = 0
} else if count[rev] > 0 {
pairs := min(c, count[rev])
length += pairs * 4
count[word] -= pairs
count[rev] -= pairs
}
}
return length
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
The Go solution mirrors the Python logic with minor language differences, including explicit byte slicing for reversing two-character strings and a helper min function.
Worked Examples
Example 1: ["lc","cl","gg"]
| Word | Count | Action | Length |
|---|---|---|---|
| lc | 1 | rev = cl exists, pair -> add 4 | 4 |
| cl | 1 | already paired | 4 |
| gg | 1 | palindromic, add 2 for middle | 6 |
Result: 6
Example 2: ["ab","ty","yt","lc","cl","ab"]
| Word | Count | Action | Length |
|---|---|---|---|
| ab | 2 | rev = ba not exists, palindromic? no | 0 |
| ty | 1 | rev = yt exists, pair -> add 4 | 4 |
| yt | 1 | paired | 4 |
| lc | 1 | rev = cl exists, pair -> add 4 | 8 |
| cl | 1 | paired | 8 |
| ab | 2 | nothing | 8 |
Result: 8
Example 3: ["cc","ll","xx"]
| Word | Count | Action | Length |
|---|---|---|---|
| cc | 1 | palindromic, add 2 | 2 |
| ll | 1 | palindromic, skipped | 2 |
| xx | 1 | palindromic, skipped | 2 |
Result: 2
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | We iterate through the list once to count words and once through unique words to pair them. |
| Space | O(n) | Hash map stores counts of each unique word, at most 26*26 = 676 entries, which is bounded but asymptotically O(n). |
The algorithm scales linearly with input size and uses extra space proportional to the number of unique words.
Test Cases
# Provided examples
assert Solution().longestPalindrome(["lc","cl","gg"]) == 6 # basic palindrome with central word
assert Solution().longestPalindrome(["ab","ty","yt","lc","cl","ab"]) == 8 # multiple pairs
assert Solution().longestPalindrome(["cc","ll","xx"]) == 2 # only one palindromic word used
# Edge cases
assert Solution().longestPalindrome(["aa"]) == 2 # single palindromic word
assert Solution().longestPalindrome(["ab","ba"]) == 4 # single pair forming palindrome
assert Solution().longestPalindrome(["ab","cd"]) == 0 # no palindromes possible
assert Solution().longestPalindrome(["aa","aa","aa"]) == 6 # multiple palindromes with central use
assert Solution().longestPalindrome(["ab","ba","cc","cc","cc"]) == 10 # mix of mirrored pairs and center
| Test | Why |
|---|---|
| ["lc","cl","gg"] | Valid palindrome with central word |
| ["ab","ty","yt","lc","cl","ab"] | Multiple mirrored pairs |
| ["cc","ll","xx"] | Only one palindromic word used |
| ["aa"] | Single palindromic word edge case |