Palindrome II

Problem Statement

Given a string s, partition s such that every substring of the partition is a palindrome.

Return the minimum cuts needed for a palindrome partitioning of s.

For example, given s = "aab",  Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.

 

First we need to give some definitions. Then

Define

  • $cut[k]$ to indicate the minimum number of cuts of the first $k$ characters in string $s$, {$s_0, s_1, ..., s_{k-1}$}.

 

  • Thus, the index of cut is one more than index of s, which means the number of minimum cut of {$s_0, ..., s_i$} will be stored at $cut[s_i]$, or $cut[i+1]$. For simplicity, we use the terminology minimum cut of $s_i$ to indicate minimum cut of {$s_0, ..., s_i$}.

 

  • So the problem can be expressed to compute $cut[n]$. 

 

Obviously, the initial $cut[i]$ should be $i - 1$. Because every node itself is a palindrome. Thus $n$ nodes at most need $n-1$ partitions:

for(int i = 0; i < n; ++i)
    cut[i] = i - 1; 

 

At first sight, the assignment $cut[0] = -1$ seems like a garbage value, but later we will explain it's very useful to assign $cut[0]$ to -1 to complete our problem.

 

In order to find some intuitions, let's think about the minimum cut of string {$s_0, s_1, ..., s_i$} with $cut[i+1]$ partitions.

Denote the cut like: {$s_0, s_1, ..., s_i$} = {$P_1, P_2, ..., P_k$} =
{
{$s_0, s_1, ..., s_{n_1}$},
{$s_{n_1+1}, s_{n_1+2}, ..., s_{n_2}$},
{...},
....,
{$s_{n_{k-2}+1}, s_{n_{k-2}+2}, ..., s_{n_{k-1}}$},
{$s_{n_{k-1}+1}, s_{n_{k-1}+2}, ..., s_{n_k}$}
}.

Immediately, we can get

  1. The node $s_i$ must be in $P_k$ with $s_i = s_{n_k}$. And $P_k$ is a palindrome.
  2. Also, consider the node $s_{n_{k-1}}$. We can state that {$P_1, P_2, ..., P_{k-1}$} is also the minimum cut of string {$s_0, s_1, ..., s_{n_{k-1}}$}.
    (Otherwise, we can use {$s_0, s_1, ..., s_{n_{k'}}$}'s minimum cut {$P_1', P_2', ..., P_{k-1}'$} $\cup$ {$P_k$} to get {$s_0, s_1, ..., s_i$}'s smaller cut, which contradicates to {$P_1, P_2, ..., P_k$}'s minimum.)
  3. Based on the above two statements, we also get $k = (k-1) + 1$, which means $cut[s_i] = cut[s_{n_{k-1}}] + 1$, or $cut[i+1] = cut[n_{k-1}+1] + 1$.
  4. The situation that the whole string {$s_0, ..., s_i$} is palindrome, i.e., $P_k =$ {$ s_0, ..., s_i $}, $P_{k-1} = \emptyset$ and $cut[i] = 0$, is also included.
    As the deductions above, $cut[i] = cut[s_{-1}] + 1 = cut[0] + 1 = -1 + 1 = 0$, which matches the result of our problem.
  5. Point 4 also explains that the initial assignment of cut[0] = -1 is useful and meaningful.

From these insights, we can get that for every minimum partition, let's say node $s_i$'s minimum cut, there exists some $j$, where $j \leq i$, such that

$s_i$'s minimum cut =

{$s_0, s_1, ..., s_{j-1}$}'s minimum cut

$\cup$

{$s_j, ..., s_i$}, where {$s_j, ..., s_i$} is a palindrome.

Thus, if we test a palindrome {$s_p, ..., s_q$}, it may be the minimum partition's last part $P_k$. So we may have a chance to update $s_q$'s minimum cut, i.e. updating cut[q+1] by $cut[s_q] = min(cut[s_q], cut[s_{p-1}]+1)$

if(isPalindrome(s, p, q))
    cut[q+1] = max(cut[q+1], cut[p]+1)

 

As every node $s_i$'s $cut[i+1]$ can only be affected by its previous nodes, we can systematically detect palindrome and then update $cut[i+1]$ from the beginning of string s.

for(int point = 0; point < n; ++point){
    //detect palindrome and update cut[i]
    ...
}

 

For every node $s_i$, we consider palindrome centralized at $s_i$. Of course, there may be two cases of palindrome centralized at $s_i$:

  • [$s_{i-j}, ..., s_i, ..., s_{i+j}$] with odd length $2j+1$.

 

  • [$s_{i-(j-1)}, ..., s_i, s_{i+1}, ..., s_{i+j}$] with even length $2j$

We can expand the half length $j$ from zero until it reaches the boundary $s_0$ or $s_{n-1}$. And once the expansion of length stops at some length $j'$, there won't exist palindrome centralized at $i$ with length greater than $j'$.

// odd length
for(int halfLength = 0; point-halfLength >= 0 && point+halfLength < n && s[point-halfLength] == s[point+halfLength])
    cut[point+halfLength+1] = min(cut[point+halfLength+1], cut[point-halfLength]+1);
    
// even length
for(int halfLength = 1; point-halfLength+1 >= 0 && point+halfLength < n && s[point-halfLength+1] == s[point+halfLength])
    cut[point+halfLength+1] = min(cut[point+halfLength+1], cut[point-halfLength+1]+1)

or if we use $i$ denote central point, and $j$ denote half length, we can get a piece of simply code:

for (int i = 0; i < n; ++i){
    //odd length
	for (int j = 0; i-j >= 0 && i+j < n && s[i-j] == s[i+j]; ++j)
	    cut[i+j+1] = min(cut[i+j+1], cut[i-j]+1);

	//even length
	for (int j = 1; i-j+1 >= 0 && i+j < n && s[i-j+1] == s[i+j]; ++j)
        cut[i+j+1] = min(cut[i+j+1], cut[i-j+1]+1);
}

 


The complete code is:

int minCut(string s) {
    int n = s.size();
	if(n <= 1) return 0;

	vector<int> cut(n+1, 0);

	for (int i = 0; i <= n; i++) 
		cut[i] = i-1;

	for (int i = 0; i < n; ++i)
	{
		//odd length, i.e. i is the middle point, [i-j, ..., i, ..., i+j];
		for (int j = 0; i-j >= 0 && i+j < n && s[i-j] == s[i+j]; ++j)
		    cut[i+j+1] = min(cut[i+j+1], cut[i-j]+1);

		//even length, i.e. i is left side's endpoint, [i-(j-1), ..., i, i+1, ..., i+j];
		for (int j = 1; i-j+1 >= 0 && i+j < n && s[i-j+1] == s[i+j]; ++j)
		    cut[i+j+1] = min(cut[i+j+1], cut[i-j+1]+1);
	}

	return cut[n];
}

The running time is about two parts. The first is the assignment of $cut[i]$, $O(n)$.

The second part is divided by two for loop:

  • $O(1 + 2 + 3 + 4 + ... + n/2) = O(n^2)$ 
  • $O(1 + 2 + 3 + 4 + ... + n/2) = O(n^2)$

So the total running time is $O(n^2)$.

And obviously the space is $O(n)$.

posted @ 2014-11-22 12:02  kid551  阅读(114)  评论(0编辑  收藏  举报