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.
分析摘自:
http://blog.youkuaiyun.com/doc_sgl/article/details/13418125
http://blog.youkuaiyun.com/yutianzuijin/article/details/16850031
对原分析有修改。
定义函数
D[i,n] = 区间[i,n]之间最小的cut数,n为字符串长度
a b a b b b a b b a b a
i n
如果现在求[i,n]之间的最优解?应该是多少?简单看一看,至少有下面一个解
a b a b b b a b b a b a
i j j+1
n
此时 D[i,n] = min(D[i, j] + D[j+1,n]) i<=j <n。
以上是容易想到的,以“acaded”为例,也可以看做下面这张表:
a | c | a | d | e | d | |
a | 0 | 1 | 0 | 1 | 2 | 1 |
c | 0 | 1 | 2 | 3 | 2 | |
a | 0 | 1 | 2 | 1 | ||
d | 0 | 1 | 0 | |||
e | 0 | 1 | ||||
d | 0 |
dp[0][5]=min{dp[0][0]+dp[1][5],dp[0][1]+dp[2][5],dp[0][2]+dp[3][5],dp[0][3]+dp[4][5]}+1
然而这种朴素的DP是O(N^3),华华丽丽TLE,没有利用好回文串的性质。
int[][] dp;
int[][] isp;
public int minCut(String s)
{
int n=s.length();
if(n<=1)
return 0;
dp=new int[n][n];
isp=new int[n][n];
for(int i=0;i<n;i++)
Arrays.fill(dp[i], -1);
for(int i=0;i<n;i++)
dp[i][i]=0;
return dp(0, n-1, s);
}
private int dp(int x,int y,String s)
{
if(dp[x][y]>-1)
return dp[x][y];
if(ispali(s.substring(x,y+1)))
return dp[x][y]=0;
int minxy=Math.min(x, y);
int min=Integer.MAX_VALUE;
for(int j=minxy,i=x+1;j<y;i++,j++)
{
int sum=dp(x, j, s)+dp(i, y, s)+1;
if(sum<min)
min=sum;
}
return dp[x][y]=min;
}
private boolean ispali(String s)
{
int strlen=s.length();
for(int i=0,j=strlen-1;i<strlen/2;i++,j--)
if(s.charAt(i)!=s.charAt(j))
return false;
return true;
}
------------------------------------------------------------------------------------------------------
换成一维DP。如果每次,从i往右扫描,每找到一个回文就算一次DP的话,就可以转换为
D[i] = 区间[i,n]之间最小的cut数,n为字符串长度, 则,
D[i] = min(1+D[j+1] ) i<=j <n 【这里不再随便切割,而是选到前面满足回文才切割】
有个转移函数之后,一个问题出现了,就是如何判断[i,j]是否是回文?每次都从i到j比较一遍?太浪费了,这里也是一个DP问题。
定义函数
P[i][j] = true if [i,j]为回文
那么
P[i][j] = ((str[i] == str[j]) && (P[i+1][j-1]));
公式的含义是,从i开始的字符串的最小划分为:如果从位置i到位置j的子串是回文串,则从i开始的划分可以通过将i到j的子串看作划分的一部分,然后加上从j+1位置开始的子串最小划分,并选择可能情况中的最小值即为从i开始的最小划分。
完成通项公式的构造,下一步是考虑计算顺序和初始化。由于count[i]依赖于比i更大的count元素,所以i循环应该倒序。j的取值范围从i到len-1,正序和倒序均可。需要初始化的元素为最后一个count元素,其满足count[len]=0。
public class Solution {
public int minCut(String s) {
int[][] dp=new int[s.length()][s.length()];
int[] count=new int[s.length()+1];
for(int i=s.length()-1;i>=0;i--)
{
count[i]=Integer.MAX_VALUE;
for(int j=i;j<s.length();j++)
{
if(s.charAt(i)==s.charAt(j)&&(j-i<2||dp[i+1][j-1]==1))
{
dp[i][j]=1;
count[i]=Math.min(1+count[j+1],count[i]);
}
}
}
return count[0]-1;
}
}
update 2016.07.22
第一次DP先判断所有[i,j]是否回文串
第二次DP,count[i] =min{ count[j-1] +1} 当 [j,i]是回文串 1<=j<=i
public int minCut(String s)
{
int len=s.length();
boolean[][] dp=new boolean[len][len];
for(int i=len-1;i>=0;i--)
for(int j=0;j<len;j++)
{
if(i>=j)
dp[i][j]=true;
else {
dp[i][j]=dp[i+1][j-1]&&(s.charAt(i)==s.charAt(j));
}
}
int[] count=new int[len+1];
Arrays.fill(count, Integer.MAX_VALUE);
count[0]=0;
for(int i=1;i<=len;i++)
for(int k=1;k<=i;k++)
if(dp[k-1][i-1]==true)
count[i]=Math.min(count[i], count[k-1]+1);
return count[len]-1;
}