Open In App

Longest Palindromic Substring using Palindromic Tree | Set 3

Improve
Improve
Like Article
Like
Save
Share
Report

Given a string, find the longest substring which is a palindrome. For example, if the given string is “forgeeksskeegfor”, the output should be “geeksskeeg”.

Prerequisite : Palindromic Tree | Longest Palindromic Substring

Structure of Palindromic Tree : 

The palindromic Tree’s actual structure is close to the directed graph. It is actually a merged structure of two Trees that share some common nodes(see the figure below for a better understanding). Tree nodes store palindromic substrings of a given string by storing their indices. This tree consists of two types of edges:

  1. Insertion edge (weighted edge)
  2. Maximum Palindromic Suffix (un-weighted)

Insertion Edge :

Insertion edge from a node u to v with some weight x means that the node v is formed by inserting x at the front and end of the string at u. As ‘u’ is already a palindrome, hence the resulting string at node v will also be a palindrome. x will be a single character for every edge. Therefore, a node can have a max of 26 insertion edges (considering lower letter string).

Maximum Palindromic Suffix Edge :

As the name itself indicates that for a node this edge will point to its Maximum Palindromic Suffix String node. We will not be considering the complete string itself as the Maximum Palindromic Suffix as this will make no sense(self-loops). For simplicity purposes, we will call it Suffix edge(by which we mean maximum suffix except for the complete string). It is quite obvious that every node will have only 1 Suffix Edge as we will not store duplicate strings in the tree. We will create all the palindromic substrings and then return the last one we got since that would be the longest palindromic substring so far. Since Palindromic Tree stores the palindromes in order of arrival of a certain character, the Longest will always be at the last index of our tree array

Below is the implementation of the above approach : 

C++




// C++ program to demonstrate working of
// palindromic tree
#include "bits/stdc++.h"
using namespace std;
 
#define MAXN 1000
 
struct Node {
    // store start and end indexes of current
    // Node inclusively
    int start, end;
 
    // stores length of substring
    int length;
 
    // stores insertion Node for all characters a-z
    int insertEdg[26];
 
    // stores the Maximum Palindromic Suffix Node for
    // the current Node
    int suffixEdg;
};
 
// two special dummy Nodes as explained above
Node root1, root2;
 
// stores Node information for constant time access
Node tree[MAXN];
 
// Keeps track the current Node while insertion
int currNode;
string s;
int ptr;
 
void insert(int idx)
{
    // STEP 1//
 
    /* search for Node X such that s[idx] X S[idx]
    is maximum palindrome ending at position idx
    iterate down the suffix link of currNode to
    find X */
    int tmp = currNode;
    while (true) {
        int curLength = tree[tmp].length;
        if (idx - curLength >= 1
            and s[idx] == s[idx - curLength - 1])
            break;
        tmp = tree[tmp].suffixEdg;
    }
 
    /* Now we have found X ....
     * X = string at Node tmp
     * Check : if s[idx] X s[idx] already exists or not*/
    if (tree[tmp].insertEdg[s[idx] - 'a'] != 0) {
        // s[idx] X s[idx] already exists in the tree
        currNode = tree[tmp].insertEdg[s[idx] - 'a'];
        return;
    }
 
    // creating new Node
    ptr++;
 
    // making new Node as child of X with
    // weight as s[idx]
    tree[tmp].insertEdg[s[idx] - 'a'] = ptr;
 
    // calculating length of new Node
    tree[ptr].length = tree[tmp].length + 2;
 
    // updating end point for new Node
    tree[ptr].end = idx;
 
    // updating the start for new Node
    tree[ptr].start = idx - tree[ptr].length + 1;
 
    // STEP 2//
 
    /* Setting the suffix edge for the newly created
    Node tree[ptr]. Finding some String Y such that
    s[idx] + Y + s[idx] is longest possible
    palindromic suffix for newly created Node.*/
 
    tmp = tree[tmp].suffixEdg;
 
    // making new Node as current Node
    currNode = ptr;
    if (tree[currNode].length == 1) {
        // if new palindrome's length is 1
        // making its suffix link to be null string
        tree[currNode].suffixEdg = 2;
        return;
    }
    while (true) {
        int curLength = tree[tmp].length;
        if (idx - curLength >= 1
            and s[idx] == s[idx - curLength - 1])
            break;
        tmp = tree[tmp].suffixEdg;
    }
 
    // Now we have found string Y
    // linking current Nodes suffix link with
    // s[idx]+Y+s[idx]
    tree[currNode].suffixEdg
        = tree[tmp].insertEdg[s[idx] - 'a'];
}
 
// driver program
int main()
{
    // initializing the tree
    root1.length = -1;
    root1.suffixEdg = 1;
    root2.length = 0;
    root2.suffixEdg = 1;
 
    tree[1] = root1;
    tree[2] = root2;
    ptr = 2;
    currNode = 1;
 
    // given string
    s = "forgeeksskeegfor";
    int l = s.length();
 
    for (int i = 0; i < l; i++)
        insert(i);
 
    int maxx = 0;
    string result = "";
   
    for (int i = 3; i <= ptr; i++) {
        string res = "";
        for (int j = tree[i].start; j <= tree[i].end; j++)
            res += s[j];
        if (res.size() > maxx) {
            maxx = res.size();
            result = res;
        }
    }
    cout << result;
 
    return 0;
}


Java




// Nikunj Sonigara
 
// Define a class named Main
class Main {
    // Define a static nested class named Node
    static class Node {
        int start, end;  // Represents the start and end indices of a palindrome substring
        int length;      // Length of the palindrome substring
        int[] insertEdge = new int[26];  // Array to store indices of child nodes for each character
        int suffixEdge;  // Index of the node representing the largest proper suffix palindrome
    }
 
    // Create two root nodes and an array to store nodes
    static Node root1 = new Node();
    static Node root2 = new Node();
    static Node[] tree = new Node[1000];
    static int ptr;  // Pointer to the current node being processed
    static int currNode;  // Index of the current node in the tree
    static String s;  // Input string
 
    // Main method
    public static void main(String[] args) {
        // Initialize the root nodes
        root1.length = -1;
        root1.suffixEdge = 1;
        root2.length = 0;
        root2.suffixEdge = 1;
 
        // Create nodes and initialize the tree array
        for (int i = 0; i < 1000; i++) {
            tree[i] = new Node();
        }
        tree[1] = root1;
        tree[2] = root2;
        ptr = 2;
        currNode = 1;
 
        // Input string
        s = "forgeeksskeegfor";
        int l = s.length();
 
        // Build the palindrome tree
        for (int i = 0; i < l; i++) {
            insert(i);
        }
 
        // Find the longest palindrome substring in the tree
        int maxx = 0;
        String result = "";
 
        for (int i = 3; i <= ptr; i++) {
            StringBuilder res = new StringBuilder();
            for (int j = tree[i].start; j <= tree[i].end; j++) {
                res.append(s.charAt(j));
            }
            if (res.length() > maxx) {
                maxx = res.length();
                result = res.toString();
            }
        }
 
        // Print the result
        System.out.println(result);
    }
 
    // Method to insert a character into the palindrome tree
    static void insert(int idx) {
        int tmp = currNode;
 
        // Traverse the tree to find the largest proper suffix palindrome
        while (true) {
            int curLength = tree[tmp].length;
            if (idx - curLength >= 1 && s.charAt(idx) == s.charAt(idx - curLength - 1)) {
                break;
            }
            tmp = tree[tmp].suffixEdge;
        }
 
        // Check if the current character already has an edge in the tree
        if (tree[tmp].insertEdge[s.charAt(idx) - 'a'] != 0) {
            currNode = tree[tmp].insertEdge[s.charAt(idx) - 'a'];
            return;
        }
 
        // Create a new node for the current character
        ptr++;
        tree[tmp].insertEdge[s.charAt(idx) - 'a'] = ptr;
        tree[ptr].length = tree[tmp].length + 2;
        tree[ptr].end = idx;
        tree[ptr].start = idx - tree[ptr].length + 1;
 
        // Update the current node and handle special case for length 1 palindrome
        tmp = tree[tmp].suffixEdge;
        currNode = ptr;
        if (tree[currNode].length == 1) {
            tree[currNode].suffixEdge = 2;
            return;
        }
 
        // Traverse the tree again to find the largest proper suffix palindrome
        while (true) {
            int curLength = tree[tmp].length;
            if (idx - curLength >= 1 && s.charAt(idx) == s.charAt(idx - curLength - 1)) {
                break;
            }
            tmp = tree[tmp].suffixEdge;
        }
 
        // Update the suffix edge for the current node
        tree[currNode].suffixEdge = tree[tmp].insertEdge[s.charAt(idx) - 'a'];
    }
}


Python3




# Python program to demonstrate working of
# palindromic tree
 
MAXN = 1000
 
 
class Node:
    def __init__(self):
        # store start and end indexes of current Node inclusively
        self.start = 0
        self.end = 0
 
        # stores length of substring
        self.length = 0
 
        # stores insertion Node for all characters a-z
        self.insertEdg = [0]*26
 
        # stores the Maximum Palindromic Suffix Node for the current Node
        self.suffixEdg = 0
 
 
# two special dummy Nodes as explained above
root1 = Node()
root2 = Node()
 
# stores Node information for constant time access
tree = [Node() for i in range(MAXN)]
 
# Keeps track the current Node while insertion
currNode = 0
s = ""
ptr = 0
 
 
def insert(idx):
    global currNode, ptr
 
    # STEP 1
 
    # search for Node X such that s[idx] X S[idx] is maximum palindrome ending at position idx
    # iterate down the suffix link of currNode to find X
    tmp = currNode
    while True:
        curLength = tree[tmp].length
        if idx - curLength >= 1 and s[idx] == s[idx - curLength - 1]:
            break
        tmp = tree[tmp].suffixEdg
 
    # Now we have found X....
    # X = string at Node tmp
    # Check: if s[idx] X s[idx] already exists or not
    if tree[tmp].insertEdg[ord(s[idx]) - ord('a')] != 0:
        # s[idx] X s[idx] already exists in the tree
        currNode = tree[tmp].insertEdg[ord(s[idx]) - ord('a')]
        return
 
    # creating new Node
    ptr += 1
 
    # making new Node as child of X with weight as s[idx]
    tree[tmp].insertEdg[ord(s[idx]) - ord('a')] = ptr
 
    # calculating length of new Node
    tree[ptr].length = tree[tmp].length + 2
 
    # updating end point for new Node
    tree[ptr].end = idx
 
    # updating the start for new Node
    tree[ptr].start = idx - tree[ptr].length + 1
 
    # STEP 2
 
    # Setting the suffix edge for the newly created Node tree[ptr]. Finding some String Y
    # such that s[idx] + Y + s[idx] is longest possible palindromic suffix for newly created Node.
    tmp = tree[tmp].suffixEdg
 
    # making new Node as current Node
    currNode = ptr
    if tree[currNode].length == 1:
        # if new palindrome's length is 1, making its suffix link to be null string
        tree[currNode].suffixEdg = 2
        return
    while True:
        curLength = tree[tmp].length
        if idx - curLength >= 1 and s[idx] == s[idx - curLength - 1]:
            break
        tmp = tree[tmp].suffixEdg
 
    # Now we have found string Y
    # linking current Nodes suffix link with s[idx]+Y+s[idx]
    tree[currNode].suffixEdg = tree[tmp].insertEdg[ord(s[idx]) - ord('a')]
 
# driver program
 
 
# initializing the tree
root1.length = -1
root1.suffixEdg = 1
root2.length = 0
root2.suffixEdg = 1
 
tree[1] = root1
tree[2] = root2
ptr = 2
currNode = 1
 
# given string
s = "forgeeksskeegfor"
l = len(s)
 
for i in range(l):
    insert(i)
 
maxx = 0
result = ""
 
for i in range(3, ptr+1):
    res = ""
    for j in range(tree[i].start, tree[i].end+1):
        res += s[j]
    if len(res) > maxx:
        maxx = len(res)
        result = res
 
print(result)


C#




using System;
 
class PalindromicTree {
    const int MAXN = 1000;
 
    // Node structure
    struct Node
    {
        // Store start and end indexes of current Node
        // inclusively
        public int start, end;
 
        // Store length of substring
        public int length;
 
        // Store insertion Node for all characters a-z
        public int[] insertEdge;
 
        // Store the Maximum Palindromic Suffix Node for the
        // current Node
        public int suffixEdge;
    }
 
    // Two special dummy Nodes as explained above
    static Node root1, root2;
 
    // Store Node information for constant time access
    static Node[] tree;
 
    // Keep track of the current Node while insertion
    static int currNode;
    static string s;
    static int ptr;
 
    // Function to insert a character into the Palindromic
    // Tree
    static void Insert(int idx)
    {
        // STEP 1
 
        /* Search for Node X such that s[idx] X s[idx]
         * is the maximum palindrome ending at position idx.
         * Iterate down the suffix link of currNode to find
         * X. */
        int tmp = currNode;
        while (true) {
            int curLength = tree[tmp].length;
            if (idx - curLength >= 1
                && s[idx] == s[idx - curLength - 1])
                break;
            tmp = tree[tmp].suffixEdge;
        }
 
        /* Now we have found X...
         * X = string at Node tmp
         * Check: if s[idx] X s[idx] already exists or not
         */
        if (tree[tmp].insertEdge[s[idx] - 'a'] != 0) {
            // s[idx] X s[idx] already exists in the tree
            currNode = tree[tmp].insertEdge[s[idx] - 'a'];
            return;
        }
 
        // Creating a new Node
        ptr++;
 
        // Initializing the insertEdge array for the new
        // Node
        tree[ptr].insertEdge = new int[26];
 
        // Making the new Node as a child of X with weight
        // as s[idx]
        tree[tmp].insertEdge[s[idx] - 'a'] = ptr;
 
        // Calculating the length of the new Node
        tree[ptr].length = tree[tmp].length + 2;
 
        // Updating the end point for the new Node
        tree[ptr].end = idx;
 
        // Updating the start for the new Node
        tree[ptr].start = idx - tree[ptr].length + 1;
 
        // STEP 2
 
        /* Setting the suffix edge for the newly created
         * Node tree[ptr]. Finding some String Y such that
         * s[idx] + Y + s[idx] is the longest possible
         * palindromic suffix for the newly created Node. */
 
        tmp = tree[tmp].suffixEdge;
 
        // Making the new Node as the current Node
        currNode = ptr;
        if (tree[currNode].length == 1) {
            // If the new palindrome's length is 1,
            // making its suffix link to be a null string
            tree[currNode].suffixEdge = 2;
            return;
        }
        while (true) {
            int curLength = tree[tmp].length;
            if (idx - curLength >= 1
                && s[idx] == s[idx - curLength - 1])
                break;
            tmp = tree[tmp].suffixEdge;
        }
 
        // Now we have found string Y
        // Linking the current Node's suffix link with
        // s[idx]+Y+s[idx]
        tree[currNode].suffixEdge
            = tree[tmp].insertEdge[s[idx] - 'a'];
    }
 
    // Driver program
    static void Main()
    {
        // Initializing the tree
        root1.length = -1;
        root1.suffixEdge = 1;
        root1.insertEdge = new int[26];
 
        root2.length = 0;
        root2.suffixEdge = 1;
        root2.insertEdge = new int[26];
 
        tree = new Node[MAXN];
        tree[1] = root1;
        tree[2] = root2;
        ptr = 2;
        currNode = 1;
 
        // Given string
        s = "forgeeksskeegfor";
        int l = s.Length;
 
        for (int i = 0; i < l; i++)
            Insert(i);
 
        int maxx = 0;
        string result = "";
 
        for (int i = 3; i <= ptr; i++) {
            string res = "";
            for (int j = tree[i].start; j <= tree[i].end;
                 j++)
                res += s[j];
            if (res.Length > maxx) {
                maxx = res.Length;
                result = res;
            }
        }
        Console.WriteLine(result);
    }
}


Javascript




// JavaScript program to demonstrate working of
// palindromic tree
const MAXN = 1000;
 
class Node {
    constructor() {
        this.start = 0; // store start and end indexes of current Node inclusively
        this.end = 0;
        this.length = 0; // stores length of substring
        this.insertEdg = Array(26).fill(0); // stores insertion Node for all characters a-z
        this.suffixEdg = 0; // stores the Maximum Palindromic Suffix Node for the current Node
    }
}
 
const root1 = new Node(); // two special dummy Nodes as explained above
const root2 = new Node();
 
const tree = Array(MAXN).fill().map(() => new Node()); // stores Node information for constant time access
let currNode = 0;
let s = "";
let ptr = 0;
 
function insert(idx) {
    let tmp;
 
    // STEP 1
 
    // search for Node X such that s[idx] X S[idx] is maximum palindrome ending at position idx
    // iterate down the suffix link of currNode to find X
    tmp = currNode;
    while (true) {
        const curLength = tree[tmp].length;
        if (idx - curLength >= 1 && s[idx] === s[idx - curLength - 1]) {
            break;
        }
        tmp = tree[tmp].suffixEdg;
    }
 
    // Now we have found X....
    // X = string at Node tmp
    // Check: if s[idx] X s[idx] already exists or not
    if (tree[tmp].insertEdg[s.charCodeAt(idx) - 97] !== 0) {
        // s[idx] X s[idx] already exists in the tree
        currNode = tree[tmp].insertEdg[s.charCodeAt(idx) - 97];
        return;
    }
 
    // creating new Node
    ptr += 1;
 
    // making new Node as child of X with weight as s[idx]
    tree[tmp].insertEdg[s.charCodeAt(idx) - 97] = ptr;
 
    // calculating length of new Node
    tree[ptr].length = tree[tmp].length + 2;
 
    // updating end point for new Node
    tree[ptr].end = idx;
 
    // updating the start for new Node
    tree[ptr].start = idx - tree[ptr].length + 1;
 
    // STEP 2
 
    // Setting the suffix edge for the newly created Node tree[ptr]. Finding some String Y
    // such that s[idx] + Y + s[idx] is longest possible palindromic suffix for newly created Node.
    tmp = tree[tmp].suffixEdg;
 
    // making new Node as current Node
    currNode = ptr;
    if (tree[currNode].length === 1) {
        // if new palindrome's length is 1, making its suffix link to be null string
        tree[currNode].suffixEdg = 2;
        return;
    }
    while (true) {
        const curLength = tree[tmp].length;
        if (idx - curLength >= 1 && s[idx] === s[idx - curLength - 1]) {
            break;
        }
        tmp = tree[tmp].suffixEdg;
    }
 
    // Now we have found string Y
    // linking current Nodes suffix link with s[idx]+Y+s[idx]
    tree[currNode].suffixEdg = tree[tmp].insertEdg[s.charCodeAt(idx) - 97];
}
 
// initializing the tree
// const root1 = new Node();
root1.length = -1;
root1.suffixEdg = 1;
// const root2 = new Node();
root2.length = 0;
root2.suffixEdg = 1;
 
tree[1] = root1
tree[2] = root2
ptr = 2;
 
currNode = 1;
 
s = "forgeeksskeegfor";
const l = s.length;
 
for (let i = 0; i < l; i++) {
    insert(i);
}
 
let maxx = 0;
let result = "";
 
for (let i = 3; i <= ptr; i++) {
    let res = "";
    for (let j = tree[i].start; j <= tree[i].end; j++) {
        res += s[j];
    }
    if (res.length > maxx) {
        maxx = res.length;
        result = res;
    }
}
 
console.log(result);
 
// Contributed by adityasha4x71


Output

geeksskeeg



Time Complexity:

The time complexity for the building process will be O(k*n), here “n” is the length of the string and ‘k‘ is the extra iterations required to find the string X and string Y in the suffix links every time we insert a character.

Let’s try to approximate the constant ‘k’. We shall consider a worst case like s = “aaaaaabcccccccdeeeeeeeeef”. In this case for similar streak of continuous characters it will take extra 2 iterations per index to find both string X and Y in the suffix links , but as soon as it reaches some index i such that s[i]!=s[i-1] the left most pointer for the maximum length suffix will reach its rightmost limit. Therefore, for all i when s[i]!=s[i-1] , it will cost in total n iterations(summing over each iteration) and for rest i when s[i]==s[i-1] it takes 2 iteration which sums up over all such i and takes 2*n iterations. Hence, approximately our complexity in this case will be O(3*n) ~ O(n). So, we can roughly say that the constant factor ‘k’ will be very less. Therefore, we can consider the overall complexity to be linear O(length of string). You may refer the reference links for better understanding.



Last Updated : 28 Nov, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads