Given a list of unique words. Find all pairs of distinct indices (i, j)
in the given list, so that the concatenation of the two words, i.e. words[i] + words[j]
is a palindrome.
Example 1:
Given words
= ["bat", "tab", "cat"]
Return [[0, 1], [1, 0]]
The palindromes are ["battab", "tabbat"]
Example 2:
Given words
= ["abcd", "dcba", "lls", "s", "sssll"]
Return [[0, 1], [1, 0], [3, 2], [2, 4]]
The palindromes are ["dcbaabcd", "abcddcba", "slls", "llssssll"]
Credits:
Special thanks to @dietpepsi for adding this problem and creating all test cases.
Analysis:
We use Trie to build a suffix tree to reduce searching range, i.e., for a word starting with 'a', we only search words ending with 'a'.
To find the pairs for each word, we need address two cases:
1. word A is word B's suffix, e.g., "cd" + "babdc". In this case, we should record that "babdc" is a palindrome, if we exclude its suffix of "dc". So we can create a list of words at any TrieNode. This list contains all words that are palindrome if excluding the suffix presented by that TrieNode.
2. word B is word A's suffix, e.g. "dcbab" + "cd". In this case, when search for "dcbab", we should check for every prefix that is also a word, e.g., "dc" of "dcbab" is the word "cd", if excluding this prefix, word A is a palindrome.
Solution:
1 public class Solution { 2 public class TrieNode { 3 TrieNode[] childs; 4 int wordInd; // index of the word ending at this 5 List<Integer> pList; // index list of the words which are palindrome if 6 // excluding the prefix on this. 7 8 public TrieNode() { 9 childs = new TrieNode[26]; 10 wordInd = -1; 11 pList = new ArrayList<Integer>(); 12 } 13 } 14 15 public List<List<Integer>> palindromePairs(String[] words) { 16 TrieNode root = new TrieNode(); 17 18 // Build the Trie 19 for (int i = 0; i < words.length; i++) { 20 addWord(words, i, root); 21 } 22 23 // Build result list 24 List<List<Integer>> resList = new ArrayList<List<Integer>>(); 25 for (int i = 0; i < words.length; i++) { 26 findPalindrome(words, i, root, resList); 27 } 28 return resList; 29 } 30 31 // Add word into Trie. 32 public void addWord(String[] words, int cur, TrieNode root) { 33 TrieNode curNode = root; 34 String word = words[cur]; 35 for (int i = word.length() - 1; i >= 0; i--) { 36 // if word(0,i) is a palindrome, i.e., after excluding current 37 // suffix, add it into pList. 38 if (isPalindrome(word, 0, i)) { 39 curNode.pList.add(cur); 40 } 41 42 // Move to next char. 43 char curChar = word.charAt(i); 44 if (curNode.childs[curChar - 'a'] == null) { 45 curNode.childs[curChar - 'a'] = new TrieNode(); 46 } 47 curNode = curNode.childs[curChar - 'a']; 48 } 49 // Record the current word's index at the last TrieNode. 50 curNode.wordInd = cur; 51 } 52 53 // find palindrome for words[cur]. 54 public void findPalindrome(String[] words, int cur, TrieNode root, List<List<Integer>> resList) { 55 TrieNode curNode = root; 56 String word = words[cur]; 57 for (int i = 0; i < word.length(); i++) { 58 // If there is a word ending at current node, check whether that 59 // word and current word are palindrome pair. 60 if (curNode.wordInd != -1 && isPalindrome(word, i, word.length() - 1)) { 61 resList.add(Arrays.asList(cur, curNode.wordInd)); 62 } 63 64 // Move to next node; if no next node then return. 65 char curChar = word.charAt(i); 66 if (curNode.childs[curChar - 'a'] == null) 67 return; 68 curNode = curNode.childs[curChar - 'a']; 69 } 70 // For the last TrieNode, its word should not be current word. 71 if (curNode.wordInd!=-1 && curNode.wordInd!=cur){ 72 resList.add(Arrays.asList(cur,curNode.wordInd)); 73 } 74 75 // After reaching the end, further check whether there are words that 76 // are palindrome if current word is a suffix. 77 for (int ind : curNode.pList) { 78 resList.add(Arrays.asList(cur, ind)); 79 } 80 } 81 82 // Returns if word is a palindrome between index i and j. 83 public boolean isPalindrome(String word, int i, int j) { 84 while (i < j) { 85 if (word.charAt(i) == word.charAt(j)) { 86 i++; 87 j--; 88 } else 89 return false; 90 } 91 return true; 92 } 93 }