3

I came across a code for palindromic pair problem, using Trie.

public static class Trie {
    int pos;
    Trie[] nodes;   // consider xyxabc. if current trie is 'a'. Then a.nodes has information. It means string after a is palindrome
    List<Integer> palins;
    public Trie() {
        pos = -1;
        nodes = new Trie[26];
        palins = new ArrayList<>();
    }
}

public static void add(Trie root, String word, int pos) {
    for (int i = word.length() - 1; i >= 0; i--) {
        char ch = word.charAt(i);
        if (isPalindrome(word, 0, i)) { // check if substring(0, i) is palindrome.
            root.palins.add(pos);
        }
        if (root.nodes[ch - 'a'] == null) {
            root.nodes[ch - 'a'] = new Trie();
        }
        root = root.nodes[ch - 'a'];
    }
    root.pos = pos; // if it is xyxcba. Until now, the node should be at x.
    root.palins.add(pos);
}

public static void search(Trie root, String[] words, int i, List<List<Integer>> ans) {
    int len = words[i].length();
    for (int j = 0; j < len && root != null; j++) {
        if (root.pos >= 0 && i != root.pos && isPalindrome(words[i], j, len - 1)) {
            ans.add(Arrays.asList(new Integer[] {i, root.pos}));
        }
        char ch = words[i].charAt(j);
        root = root.nodes[ch - 'a'];
    }
    if (root != null && root.palins.size() > 0) { // assume 'xyxabc' is in trie, now try 'cba'
        for (int j : root.palins) {
            if (j != i) {
                ans.add(Arrays.asList(new Integer[] {i, j}));
            }
        }
    }
}

public static List<List<Integer>> palindromePairs(String[] words) {
    List<List<Integer>> ans = new ArrayList<>();
    Trie trie = new Trie();
    for (int i = 0; i < words.length; i++) {
        add(trie, words[i], i);
    }
    for (int i = 0; i < words.length; i++) {
        search(trie, words, i, ans);
    }
    return ans;
}

public static boolean isPalindrome(String str, int i, int j) {
    while (i < j) {
        if (str.charAt(i++) != str.charAt(j--)) {
            return false;
        }
    }
    return true;
}

Could any one please help me understand, what are we trying to do in this line in add method.

if (isPalindrome(word, 0, i)) { // check if substring(0, i) is palindrome.
            root.palins.add(pos);
        }

and here outside of FOR loop why do we need to add:

root.palins.add(pos);

I have seen this code from here: http://www.allenlipeng47.com/blog/index.php/2016/03/15/palindrome-pairs/

However i am still finding it difficult to understand this approach.

Thanks

Aman
  • 159
  • 2
  • 15

2 Answers2

7

I think the blog post you're linking to does not do a good job explaining the core idea of the solution. This is what probably makes it difficult to understand the code.

Let's start with the problem (as you're not quoting it):

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"]

When do two words words[i] and words[j] fulfill the condition of words[i] + words[j] to be a palindrome?

  • If words[i] and words[j] have equal length, then words[i] must equals the revers of words[j] (or vice versa).
  • If one of the words (say, words[i]) is longer, then it must end with the reverse of words[j] and the rest of words[i] must be a palindrome.
    For example, take lls and s. lls ends with s and ll is in palindrome. So s + lls is the palindrome.

Having noticed this, we basically need an efficient method to check if some words[i] ends with the reverse of words[j] and that the remainder of words[i] is a palindrome.

This is implemented using a Trie structure which builds a radix tree over reversed words. The blog post gives a good example of the build Trie for words "cbaaa", "bc", "abc":

Trie for words <code>"cbaaa", "bc", "abc"</code>

This Trie makes it pretty efficient to search for prefixes. For instance searching for cba (reverse of abc) gives the node starting cbaaa.

I think the main problem understanding the code is due to poorly named fields (and the code is generally a bit messy). It is not clear what pos and palins are so this probably causes you trouble.

pos of the given Trie node is simply the index of the word formed by sequence of nodes from the "root" to the given node. In the example above, cbaaa has the index 0, bc the index 1 and abc the index 2. Corresponding nodes have the appropriate pos values.

palins is a bit more interesting. When building the Trie up to some node, we have some common prefix. For instance, when the Trie is built up to the node a in c-b-a-a-a we have the common prefix cba. But what we also need to check quickly is that the rest of the word is palindrom. This is what palins is for. palins saves indices of words which start with the common prefix we have so far in construction AND where the rest of the word is a palindrom. In our example, cbaaa starst with cba and the remainder aa is a palindrom. So the index 0 of cbaaa in words will be added to palins. If there were something like cbatt, its index would have also added to palins.
We also need to consider the case like abc - cba where there is no remainder. This is done by adding the index of the word to palins of the node of the last symbol (this is why you see another root.palins.add(pos); outside the for loop).

Now it should be more or less clear how the search method works: * For each word, search the prefix, equal to this word reversed. If there is such prefix, there will be a corresponding node in the Trie. * If such node is found, check palins. This will effectively give indices of workds which are palindrome-pairs to the given word.

For example, when we search for abc, we'll find the first a node in c-b-a-a-a. The prefix cba equals abc in reverse. The palins of this a node will contain 0 (index of cbaaa in words), so we'll get [2, 0] as one of the results.

lexicore
  • 42,748
  • 17
  • 132
  • 221
0

const words = ["abcd", "dcba", "lls", "s", "sssll"] 

const Name =(arr)=>{
  const output =[];
  for(let i=0; i<arr.length;i++){
  let word1 = arr[i];
  for(let j=0; j<arr.length; j++){
    if(i!==j){
      const concatword = word1 + arr[j];
      const reverse = concatword.split("").reverse().join("");
      if(concatword===reverse) output.push([i,j])
    } 
  }
}
  return output
}
console.log(Name(words))
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 26 '22 at 15:24