問題の説明
文字列sを指定します。各部分文字列が回文になるように、sをいくつかの部分文字列に分割してください。
要件を満たす分割の最小数を返します。
例1:
输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
例2:
输入:s = "a"
输出:0
例3:
输入:s = "ab"
输出:1
促す:
1 <= s.length <= 2000
s 仅由小写英文字母组成
問題解決のアイデア
f [i]が、文字列の接頭辞s [0 ... i]の最小分割数を表すとします。f [i]の値を取得するには、最後の回文をs [0…i]で割って列挙することを検討できます。これにより、状態遷移方程式を記述できます。
つまり、最後の回文を列挙します。j+の開始位置1、s [j + 1 ... i]が回文であることを確認するために、f [i]をf [j]から転送し、分割数を追加することができます。
上記の状態遷移方程式では、ケースをまだ考慮していないことに注意してください。つまり、s [0 ... i]自体が回文文字列です。現時点では、分割する必要はありません。つまり、次のようになります。
f [i] = 0
では、s [j + 1 ... i]とs [0 ... i]のどちらが回文であるかをどうやって知るのでしょうか?Leetcode Problem Solution 5-Longest Palindrome Substringと同じ前処理方法を使用して、文字列ssの各部分文字列が回文であるかどうかを事前に計算できます。
コード
class Solution {
public boolean f[][]; //判断以i为起点,j为终点的字符串是否是回文串
public int n; //字符串s的长度
public int dp[]; //dp[i]表示以i为最后一个元素的子字符串的最少分割次数
public int minCut(String s) {
n=s.length();
//按最长回文子串的思路填充f[][]
f=new boolean[n][n];
if(n==0 || n==1){
return 0;
}
char []cs=s.toCharArray();
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
if(i-j<=1){
if(cs[i]==cs[j]){
f[j][i]=true;
}
}else{
if(cs[i]==cs[j] && f[j+1][i-1]==true){
f[j][i]=true;
}
}
}
}
dp=new int[n];
//计算以每个元素为后缀的子字符串的最少分割次数
for(int i=0;i<n;i++){
dp[i]=i; //最坏情况下,默认每个字符都要分割
//如果从第一个元素一直到最后一个元素都是回文子串,那么最少分割次数是0
if(f[0][i]==true){
dp[i]=0;
continue;
}
//否则的话,从前往后遍历,如果从j+1到i可以组成一个回文子串
//那么最少分割次数就等于0到j的最少分割次数+1
for(int j=0;j<i;j++){
if(f[j+1][i]==true){
dp[i]=Math.min(dp[j]+1,dp[i]);
}
}
}
//最终返回dp[n-1]
return dp[n-1];
}
}