🧠
Trie-Based Solution with Palindrome Suffix Tracking
💡 This approach uses a trie to store reversed words and tracks palindrome suffixes to find pairs efficiently, demonstrating advanced data structure usage.
Intuition
Insert reversed words into a trie, storing indices of words whose suffixes form palindromes. For each word, traverse the trie to find matching prefixes and use stored palindrome suffix indices to find pairs.
Algorithm
- Build a trie by inserting reversed words, storing indices and palindrome suffix info at nodes
- For each word, traverse the trie matching characters
- If at any point the remaining substring is palindrome, add pairs using stored indices
- After traversal, add pairs from palindrome suffix indices stored at the current node
💡 The complexity lies in managing the trie and palindrome suffix lists, but it reduces redundant checks.
class TrieNode:
def __init__(self):
self.children = {}
self.index = -1
self.palindrome_suffixes = []
def is_palindrome(word):
return word == word[::-1]
def palindromePairs(words):
root = TrieNode()
def add_word(word, index):
node = root
for i, ch in enumerate(reversed(word)):
if is_palindrome(word[:len(word)-i]):
node.palindrome_suffixes.append(index)
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.index = index
node.palindrome_suffixes.append(index)
def search(word, index):
res = []
node = root
for i, ch in enumerate(word):
if node.index >= 0 and node.index != index and is_palindrome(word[i:]):
res.append([index, node.index])
if ch not in node.children:
return res
node = node.children[ch]
for j in node.palindrome_suffixes:
if j != index:
res.append([index, j])
return res
for i, word in enumerate(words):
add_word(word, i)
result = []
for i, word in enumerate(words):
result.extend(search(word, i))
return result
# Example usage
if __name__ == '__main__':
words = ["abcd","dcba","lls","s","sssll"]
print(palindromePairs(words))
Line Notes
class TrieNode:Defines trie node structure with children, index, and palindrome suffix list
def add_word(word, index):Inserts reversed word into trie, tracking palindrome suffixes
if is_palindrome(word[:len(word)-i]):Check if prefix of reversed word is palindrome to store suffix info
node.index = indexMark node as end of word with index
def search(word, index):Search trie for pairs matching current word
if node.index >= 0 and node.index != index and is_palindrome(word[i:]):Check if remaining substring is palindrome to add pair
for j in node.palindrome_suffixes:Add pairs from stored palindrome suffix indices
result.extend(search(word, i))Collect all pairs found for current word
import java.util.*;
class TrieNode {
Map<Character, TrieNode> children = new HashMap<>();
int index = -1;
List<Integer> palindromeSuffixes = new ArrayList<>();
}
public class PalindromePairs {
public static boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
if (s.charAt(left) != s.charAt(right)) return false;
left++;
right--;
}
return true;
}
TrieNode root = new TrieNode();
public void addWord(String word, int index) {
TrieNode node = root;
for (int i = word.length() - 1; i >= 0; i--) {
if (isPalindrome(word.substring(0, i + 1))) {
node.palindromeSuffixes.add(index);
}
char ch = word.charAt(i);
node.children.putIfAbsent(ch, new TrieNode());
node = node.children.get(ch);
}
node.index = index;
node.palindromeSuffixes.add(index);
}
public List<List<Integer>> search(String word, int index) {
List<List<Integer>> res = new ArrayList<>();
TrieNode node = root;
for (int i = 0; i < word.length(); i++) {
if (node.index >= 0 && node.index != index && isPalindrome(word.substring(i))) {
res.add(Arrays.asList(index, node.index));
}
char ch = word.charAt(i);
if (!node.children.containsKey(ch)) return res;
node = node.children.get(ch);
}
for (int j : node.palindromeSuffixes) {
if (j != index) res.add(Arrays.asList(index, j));
}
return res;
}
public List<List<Integer>> palindromePairs(String[] words) {
for (int i = 0; i < words.length; i++) {
addWord(words[i], i);
}
List<List<Integer>> result = new ArrayList<>();
for (int i = 0; i < words.length; i++) {
result.addAll(search(words[i], i));
}
return result;
}
public static void main(String[] args) {
PalindromePairs sol = new PalindromePairs();
String[] words = {"abcd","dcba","lls","s","sssll"};
System.out.println(sol.palindromePairs(words));
}
}
Line Notes
class TrieNode {Trie node with children map, index, and palindrome suffix list
public void addWord(String word, int index) {Insert reversed word into trie with palindrome suffix tracking
if (isPalindrome(word.substring(0, i + 1))) {Check prefix palindrome to store suffix info
node.index = index;Mark end of word with index
public List<List<Integer>> search(String word, int index) {Search trie for palindrome pairs
if (node.index >= 0 && node.index != index && isPalindrome(word.substring(i))) {Check remaining substring palindrome to add pair
for (int j : node.palindromeSuffixes) {Add pairs from stored palindrome suffix indices
result.addAll(search(words[i], i));Collect all pairs for current word
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;
struct TrieNode {
unordered_map<char, TrieNode*> children;
int index = -1;
vector<int> palindromeSuffixes;
};
bool isPalindrome(const string& s) {
int left = 0, right = s.size() - 1;
while (left < right) {
if (s[left] != s[right]) return false;
left++;
right--;
}
return true;
}
class Solution {
TrieNode* root = new TrieNode();
void addWord(const string& word, int index) {
TrieNode* node = root;
for (int i = word.size() - 1; i >= 0; i--) {
if (isPalindrome(word.substr(0, i + 1))) {
node->palindromeSuffixes.push_back(index);
}
char ch = word[i];
if (!node->children.count(ch)) {
node->children[ch] = new TrieNode();
}
node = node->children[ch];
}
node->index = index;
node->palindromeSuffixes.push_back(index);
}
vector<vector<int>> search(const string& word, int index) {
vector<vector<int>> res;
TrieNode* node = root;
for (int i = 0; i < word.size(); i++) {
if (node->index >= 0 && node->index != index && isPalindrome(word.substr(i))) {
res.push_back({index, node->index});
}
char ch = word[i];
if (!node->children.count(ch)) return res;
node = node->children[ch];
}
for (int j : node->palindromeSuffixes) {
if (j != index) res.push_back({index, j});
}
return res;
}
public:
vector<vector<int>> palindromePairs(vector<string>& words) {
for (int i = 0; i < words.size(); i++) {
addWord(words[i], i);
}
vector<vector<int>> result;
for (int i = 0; i < words.size(); i++) {
auto pairs = search(words[i], i);
result.insert(result.end(), pairs.begin(), pairs.end());
}
return result;
}
};
int main() {
vector<string> words = {"abcd","dcba","lls","s","sssll"};
Solution sol;
vector<vector<int>> result = sol.palindromePairs(words);
for (auto& pair : result) {
cout << "[" << pair[0] << "," << pair[1] << "] ";
}
cout << endl;
return 0;
}
Line Notes
struct TrieNode {Trie node with children map, index, and palindrome suffix vector
void addWord(const string& word, int index) {Insert reversed word with palindrome suffix tracking
if (isPalindrome(word.substr(0, i + 1))) {Check prefix palindrome to store suffix info
node->index = index;Mark end of word with index
vector<vector<int>> search(const string& word, int index) {Search trie for palindrome pairs
if (node->index >= 0 && node->index != index && isPalindrome(word.substr(i))) {Check remaining substring palindrome
for (int j : node->palindromeSuffixes) {Add pairs from stored palindrome suffix indices
result.insert(result.end(), pairs.begin(), pairs.end());Collect all pairs for current word
class TrieNode {
constructor() {
this.children = new Map();
this.index = -1;
this.palindromeSuffixes = [];
}
}
function isPalindrome(s) {
let left = 0, right = s.length - 1;
while (left < right) {
if (s[left] !== s[right]) return false;
left++;
right--;
}
return true;
}
class PalindromePairs {
constructor() {
this.root = new TrieNode();
}
addWord(word, index) {
let node = this.root;
for (let i = word.length - 1; i >= 0; i--) {
if (isPalindrome(word.substring(0, i + 1))) {
node.palindromeSuffixes.push(index);
}
const ch = word[i];
if (!node.children.has(ch)) {
node.children.set(ch, new TrieNode());
}
node = node.children.get(ch);
}
node.index = index;
node.palindromeSuffixes.push(index);
}
search(word, index) {
const res = [];
let node = this.root;
for (let i = 0; i < word.length; i++) {
if (node.index >= 0 && node.index !== index && isPalindrome(word.substring(i))) {
res.push([index, node.index]);
}
const ch = word[i];
if (!node.children.has(ch)) return res;
node = node.children.get(ch);
}
for (const j of node.palindromeSuffixes) {
if (j !== index) res.push([index, j]);
}
return res;
}
palindromePairs(words) {
for (let i = 0; i < words.length; i++) {
this.addWord(words[i], i);
}
const result = [];
for (let i = 0; i < words.length; i++) {
result.push(...this.search(words[i], i));
}
return result;
}
}
// Example usage
const words = ["abcd","dcba","lls","s","sssll"];
const sol = new PalindromePairs();
console.log(sol.palindromePairs(words));
Line Notes
class TrieNode {Trie node with children map, index, and palindrome suffix array
addWord(word, index) {Insert reversed word with palindrome suffix tracking
if (isPalindrome(word.substring(0, i + 1))) {Check prefix palindrome to store suffix info
node.index = index;Mark end of word with index
search(word, index) {Search trie for palindrome pairs
if (node.index >= 0 && node.index !== index && isPalindrome(word.substring(i))) {Check remaining substring palindrome
for (const j of node.palindromeSuffixes) {Add pairs from stored palindrome suffix indices
result.push(...this.search(words[i], i));Collect all pairs for current word
TimeO(n * k^2) where n is number of words and k is max word length
SpaceO(n * k) for trie nodes and palindrome suffix lists
Building and searching trie involves checking palindrome substrings and traversing characters, each O(k^2) per word due to palindrome checks at each character.
💡 Though complex, this approach is efficient and scalable for large inputs with many words.
Interview Verdict: Accepted and optimal for large inputs
This approach demonstrates mastery of tries and palindrome properties, often expected in top-tier interviews.