Tuesday 22 April 2014

Discussing Coding Interview Questions from Google, Amazon, Facebook, Microsoft, etc

No. 43 - Minimal Number of Palindromes on a String


Problem: A string can be partitioned into some substrings, such that each substring is a palindrome. For example, there are a few strategies to split the string “abbab” into palindrome substrings, such as: “abba”|”b”, “a”|”b”|”bab” and “a”|”bb”|”a”|”b”.

Given a string str, please get the minimal numbers of splits to partition it into palindromes. The minimal number of splits to partition the string “abbab” into a set of palindromes is 1.

Analysis: This is a typical problem which can be solved by dynamic programming. We have two strategies to analyze and solve this problem

Solution 1: Split at any space between two characters

Given a substring of str, starting from the index i and ending at the index (denoted as str[i:j]), we define a function f(ij) to denote the minimal number of splits to partition the substring str[i:j] into a set of palindromes. If the substring is a palindrome itself, we don’t have to split so f(ij) is 0. If the substring is not a palindrome, the substring is split between two characters k and k+1. f(ij)= f(i,k)+ f(k+1, j)+1 under such conditions. Therefore, f(ij) can be defined with the following equation:

The value of f(0, n-1) is the value of the minimal number of splits to partition str into palindromes, ifn is the length of str.

If the equation is calculated recursively, its complexity grows exponentially with the length n. A better choice is to calculate in bottom-up order with a 2D matrix with size n×n. The following C++ code implements this solution:

int minSplit_1(const string& str)
{
    int length = str.size();
    int* split = new int[length * length];
   
    for(int i = 0; i < length; ++i)
        split[i * length + i] = 0;

    for(int i = 1; i < length; ++i)
    {
        for(int j = length - i; j > 0; --j)
        {
            int row = length - i - j;
            int col = row + i;
            if(isPalindrome(str, row, col))
            {
                split[row * length + col] = 0;
            }
            else
            {
                int min = 0x7FFFFFFF;
                for(int k = row; k < col; ++k)
                {
                    int temp1 = split[row * length + k];
                    int temp2 = split[(k + 1) * length + col];
                    if(min > temp1 + temp2 + 1)
                        min = temp1 + temp2 + 1;
                }
                split[row * length + col] = min;
            }
        }
    }

    int minSplit = split[length - 1];
    delete[] split;
    return minSplit;
}

Solution 2: Split only before a palindrome

We split the string str with another strategy. Given a substring ending at the index istr[0, i], we do not have to split if the substring is a palindrome itself. Otherwise it is split between two characters at index j and j+1 only if the substring str[j+1,i] is a palindrome. Therefore, an equation f(i) can be defined as the following:

The value of f(n-1) is the value of the minimal number of splits to partition str into palindromes, if nis the length of str.

We could utilize a 1D array to solve this equation in bottom-up order, as listed in the following code:

int minSplit_2(const string& str)
{
    int length = str.size();
    int* split = new int[length];
    for(int i = 0; i < length; ++i)
        split[i] = i;

    for(int i = 1; i < length; ++i)
    {
        if(isPalindrome(str, 0, i))
        {
            split[i] = 0;
            continue;
        }

        for(int j = 0; j < i; ++j)
        {
            if(isPalindrome(str, j + 1, i) && split[i] > split[j] + 1)
                split[i] = split[j] + 1;
        }
    }

    int minSplit = split[length - 1];
    delete[] split;
    return minSplit;
}

Optimization to verify palindromes:

Usually it costs O(n) time to check whether a string with length n is a palindrome, and the typical implementation looks like the following code:

bool isPalindrome(const string& str, int begin, int end)
{
    for(int i = begin; i < end - (i - begin); ++i)
    {
        if(str[i] != str[end - (i - begin)])
            return false;
    }

    return true;
}

Both solutions above cost O(n3) time. The first solution contains three nesting for-loops. The function isPalindrome is inside two nesting for-loops.

If we could reduce the cost of isPalindrome to O(1), the time complexity of the second solution would be O(n2).

Notice that the substring str[i,j] is a palindrome only if the characters at index i and j, and str[i+1,j-1] is also a palindrome. We could build a 2D table accordingly to store whether every substring ofstr is a palindrome or not during the preprocessing. With such a table, the function isPalindrome can verify the substring str[i,j] in O(1) time.

No comments:

Post a Comment