Saturday, March 29, 2014

LeetCode: Longest Palindromic Substring

Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.


One idea is to test every possible palindrome substring and get the longest one at last. For each index i, the center of palindrome could be s[i] or s[i] and s[i+1]. We can test these two cases and find the longest one. The running time for this one is O(n^2)

Another solution is Manacher's Algorithm, and the description is in the reference link below.

C++ Code:

/*
 *func: longest_palindrome_center
 *goal: find the longest palindrome based on the input center
 *@param s: the input string
 *@param center_l: the index of left centroid
 *@param center_r: the index of right centroid
 *return: the current longest palindrome
 */
string longest_palindrome_center(const string &s, int center_l, int center_r){
    assert(center_l >= 0 && center_l < s.length()), assert(center_r < s.length() && center_r >= 0);
    assert(center_l <= center_r);
    
    //If the current pair is equal, expand to the next pair
    while(center_l >= 0 && center_r < s.length() && s[center_l] == s[center_r]){
        --center_l;
        ++center_r;
    }
    
    return s.substr(center_l + 1, center_r - center_l - 1);
}

/*
 *func: longest_palindrome
 *goal: find the longest palindrome in the input string
 *@param s: the input string
 *return: the longest palindrome
 */
/*
 *basic idea: check the length of every possible palindrome
 *complexity: time O(n^2), space O(n)
 */
string longest_palindrome(const string &s){
    string result = "";
    //Corner Case 1: the string is null
    if(s.length() == 0){
        return result;
    }
    //Else the result at least contains 1 letter
    result = s.substr(0, 1);
    
    for(int i=0; i<s.size()-1; i++){
        //For the case that the palindrome is odd-length
        string palindrome_odd = longest_palindrome_center(s, i, i);
        //Update the result
        if(palindrome_odd.length() > result.length()){
            result = palindrome_odd;
        }
        
        //For the case that the palindrome is even-length
        string palindrome_even = longest_palindrome_center(s, i, i+1);
        //Update the result
        if(palindrome_even.length() > result.length()){
            result = palindrome_even;
        }
            
    }
    
    return result;
}
/*
 *func: manacher_helper
 *goal: insert special character into the string to simplify the problem
 *For example: "aba" to "^#a#b#a#$', assume these characters don't exist
 *in the string
 *@param s: the input string
 *return: string after processing
 */
string manacher_helper(const string &s){
    //Corner Case
    if(s.length() == 0){
        return "^$";
    }
    
    string result = "^";
    for(int i=0; i<s.length(); i++){
        result += "#" + s.substr(i,1);
    }
    result += "#$";
    return result;
}

/*
 *func: longest_palindrome
 *goal: find the longest palindrome in the input string
 *@param s: the input string
 *return: the longest palindrome
 */
/*
 *idea: Manacher's Algorithm
 *complexity: time O(n), space O(n)
 */
string longest_palindrome(const string &s){
    //string after pre-processing
    string curr = manacher_helper(s);
    
    int length = curr.length();
    
    //array for the panlindrome length
    int *p = new int[length];
    
    //the index of current center, the index of right bound of current palindrome
    int center = 0, right_bound = 0;
    
    for(int i = 1; i < length-1; ++i){
        //the mirror position of current position i
        int i_mirror = 2 * center - i;
        
        //if the current right bound is greater than i, then we set
        //p[i] to the min of p[i_mirror] and right_bound-i
        //For example:
        //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
        //^ # a # b # a # b # a  #  b  #  $
        //0 0 1 0 3 0
        //assume center is 4, and right_bound is 7, and current i is 6
        //i_mirror is 2, then p[i] will be 1
        //Another case:
        //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
        //^ # b # a # b # c # b  #  a  #  b  #  c  #  b  #  a  #  $
        //0 0 1 0 3 0 1 0 7 0 1  0  9  0  1  0
        //assume center is 12, and right_bound is 21, and current i is 16
        //i_mirror is 8, then p[i] will be 5 but not 7
        p[i] = (right_bound > i) ? min(right_bound-i, p[i_mirror]) : 0;
        
        //Expand palindrome centered at i
        while(curr[i + 1 + p[i]] == curr[i - 1 - p[i]]){
            p[i]++;
        }
        
        //If palindrome centered at i expand across right bound,
        //adjust center based on expanded palindrome
        if(i + p[i] > right_bound){
            center = i;
            right_bound = i + p[i];
        }
    }
    
    //Find the maximum element in p
    int max_len = 0;
    int center_index = 0;
    for(int i = 1; i < length-1; ++i){
        if(p[i] > max_len){
            max_len = p[i];
            center_index = i;
        }
    }
    
    //Clean array p
    delete[] p;
    
    return s.substr((center_index - 1 - max_len)/2, max_len);
    
}

Python Code:

#func: find the longest palindrome substring based on the current center
#@param s: the input string
#@param center_l: the index of left centroid
#@param center_r: the index of right centroid
#@return: the longest palindrome substring
def longest_palindrome_center(s, center_l, center_r):
    assert 0 <= center_l < len(s)
    assert 0 <= center_r < len(s)
    assert center_l <= center_r

    while center_l >= 0 and center_r < len(s) and s[center_l] == s[center_r]:
        center_l -= 1
        center_r += 1

    return s[center_l+1: center_r]

#func: find the longest palindrome substring in the input string
#@param s: the input string
#@return: the longest palindrome substring
#complexity: time O(n^2), space O(n)
def longest_palindrome(s):
    #Corner Case: the string is none
    if not s:
        return ''

    #Initialize the result to the first element of the string
    result = s[0]

    for i in xrange(len(s)-1):
        #get the palindrome of odd length
        palindrome_odd = longest_palindrome_center(s, i, i)

        result = palindrome_odd if len(palindrome_odd) > len(result) else result

        #get the palindrome of even length
        palindrome_even = longest_palindrome_center(s, i, i+1)

        result = palindrome_even if len(palindrome_even) > len(result) else result

    return result

Python Code:

#func: preprocessing for manacher algorithm
#example: aba ==> ^#a#b#a#$
#@param s: input string s
#@return: string after preprocessing
def manacher_helper(s):
    #Coner Case:the string is none
    if not s:
        return '^$'

    result = '^'
    for char in s:
        result += '#' + char

    result += '#$'

    return result


#func: find the longest palindrome substring using Manacher's algorithm
#@param s: input string s
#@return: longest palindrome substring
#complexity: time O(n), space O(n)
def longest_palindrome(s):
    #string after preprocessing
    curr = manacher_helper(s)

    #auxiliary list
    p = len(curr) * [0]

    #the index of current center
    center = 0
    #the index of right bound of the current palindrome
    right_bound = 0

    for i in xrange(1, len(curr)-1):
        #the mirror position of current position i
        i_mirror = 2*center - i;

        #if the current right bound is greater than i, then we set
        #p[i] to the min of p[i_mirror] and right_bound-i
        #For example:
        #0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
        #^ # a # b # a # b # a  #  b  #  $
        #0 0 1 0 3 0
        #assume center is 4, and right_bound is 7, and current i is 6
        #i_mirror is 2, then p[i] will be 1
        #Another case:
        #0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
        #^ # b # a # b # c # b  #  a  #  b  #  c  #  b  #  a  #  $
        #0 0 1 0 3 0 1 0 7 0 1  0  9  0  1  0
        #assume center is 12, and right_bound is 21, and current i is 16
        #i_mirror is 8, then p[i] will be 5 but not 7
        p[i] = min(right_bound-i, p[i_mirror]) if right_bound > i else 0

        #Expand palindrome centered at i
        while curr[i+1+p[i]] == curr[i-1-p[i]]:
            p[i] += 1

        #If palindrome centered at i expand across right bound
        #adjust current center based on expanded palindrome
        max_len = 0
        center_index = 0
        for i in xrange(1, len(curr)-1):
            if p[i] > max_len:
                max_len = p[i]
                center_index = i

    return s[(center_index-1-max_len)/2 : (center_index-1+max_len)/2]

Reference Link:

1.Longest Palindromic Substring Part I: http://leetcode.com/2011/11/longest-palindromic-substring-part-i.html

2.Longest Palindromic Substring Part II: http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html

3.Longest palindromic substring: http://en.wikipedia.org/wiki/Longest_palindromic_substring#Manacher.27s_Algorithm_for_Finding_a_Longest_Palindrome_in_a_String

No comments:

Post a Comment