Question
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.
Stats
Frequency | 3 |
Difficulty | 4 |
Adjusted Difficulty | 5 |
Time to use | -------- |
Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)
Analysis
This is DP, but not traditional DP question
IT IS DOUBLE DP!
It is very hard for me to even understand the solution, but I have found a great analysis from peking2’s blog.
这题一般人一看就是 DP,DP 公式也很容易推出,算是一道简单的 DP。
dp(i) = min( 1+dp(j+1), if substring(i,j) is palindrome)
从以上的分析时间复杂度为 O(n^3), 主要是因为检查回文也需要 O(n)的时间。因此这题有意思的一点就是如何降低时间复杂度到 O(n^2)?
其实这题是两个 DP 混杂在了一起,这也是这道题最有意思的地方。另外一个 DP 就是跟检查回文有关了,公式如下
dp(i)(j)=true if s(i)==s(j) && dp(i+1)(j-1)
也就是说,你要检查一个回文只需要知道头尾的字符相等,并且中间的字串已经成为了回文即可。O(1)复杂度。
A more detailed analysis is available in this blog.
<b>[Thoughts]</b> <br>凡是求最优解的,一般都是走DP的路线。这一题也不例外。首先求dp函数, <br> <br>定义函数 <br>D[i,n] = 区间[i,n]之间最小的cut数,n为字符串长度 <br> <br> a b a b b b a b b a b a <br> i n <br>如果现在求[i,n]之间的最优解?应该是多少?简单看一看,至少有下面一个解 <br> <br> <br> a b a b b b a b b a b a <br> i <span style="color: red;">j</span> <span style="color: red;">j+1</span> n <br> <br>此时 D[i,n] = min(D[i, j] + D[j+1,n]) i<=j <n。这是个二维的函数,实际写代码时维护比较麻烦。所以要转换成一维DP。如果每次,从i往右扫描,每找到一个回文就算一次DP的话,就可以转换为 <br>D[i] = 区间[i,n]之间最小的cut数,n为字符串长度, 则, <br> <br>D[i] = min(1+D[j+1] ) i<=j <n <br> <br>有个转移函数之后,一个问题出现了,就是如何判断[i,j]是否是回文?每次都从i到j比较一遍?太浪费了,这里也是一个DP问题。 <br>定义函数 <br>P[i][j] = true if [i,j]为回文 <br> <br>那么 <br>P[i][j] = str[i] == str[j] && P[i+1][j-1]; <br>
Solution
The coding is not easy, especially when 2 DP are written in 1 for-loop.
I wrote many times until I finally achieved the nice and concise solution that you see below.
Code
Doing everything in 1 loop, not an intuitive code.
public int minCut(String s) {
int len = s.length();
if (len <= 1) return 0;
boolean[][] pl = new boolean[len][len];
int[] dp = new int[len];
for (int i = len-1; i >= 0; i --) {
dp[i] = Integer.MAX_VALUE;
for (int j = i; j < len; j ++) {
// first set pl[][], then update dp[i]
if (j - i <= 1) pl[i][j] = s.charAt(i) == s.charAt(j);
else pl[i][j] = s.charAt(i) == s.charAt(j) & pl[i+1][j-1];
if (pl[i][j]) {
if (j == len-1) dp[i] = 0;
else
dp[i] = Math.min(dp[i], dp[j+1] + 1);
}
}
}
return dp[0];
}
Updated on July 18th, 2014, written by me.
boolean[][] map = null;
public int minCut(String s) {
if (s == null || s.length() == 0) {
return 0;
}
map = getMap(s);
int len = s.length();
int[] dp = new int[len + 1];
dp[0] = -1;
for (int i = 0; i < len; i++) {
dp[i+1] = Integer.MAX_VALUE;
for (int j = 0; j <= i; j++) {
if (map[j][i]) {
dp[i+1] = Math.min(dp[i+1], dp[j] + 1);
}
}
}
return dp[len];
}
private boolean[][] getMap(String s) {
int len = s.length();
boolean[][] map = new boolean[len][len];
for (int i = len - 1; i >= 0; i--) {
for (int j = 0; j < len; j++) {
if (i > j) {
continue;
} else if (i == j) {
map[i][j] = true;
} else {
if (i + 1 == j) {
map[i][j] = s.charAt(i) == s.charAt(j);
} else {
map[i][j] = s.charAt(i) == s.charAt(j) && map[i+1][j-1];
}
}
}
}
return map;
}