难度:困难。
标签:
回溯法
将LeetCode132题的代码拿来改了一下,可以求出结果。
这个版本我没有提交,目测肯定超时的。
代码如下:
class Solution {
int result;
bool isPalindromic(string s, int left, int right){
if(right == left)return true;
int mid = left + (right - left + 1) / 2;
for(int i = left; i < mid ; i++){
if(s[i] != s[right - (i - left)])return false;
}
return true;
}
void backtrace(string s, int& k, int m){
int n = s.length();
if(m == n){
result = min(result, k);
return;
}
for(int i = m; i < n; i++){
if(isPalindromic(s, m, i)){
k++;
backtrace(s, k, i + 1);
k--;
}
}
}
public:
int minCut(string s) {
int n = s.length();
if(n <= 1)return 0;
result = n;
int k = 0;
backtrace(s, k, 0);
return result - 1;
}
};
动态规划
将上述回溯的代码改为动态规划超时了。使用 d p [ i ] [ j ] dp[i][j] dp[i][j]存储 i i i到 j j j的字符串是否为回文串。
超时代码:
class Solution {
int result;
void backtrace(int n, int& k, int m, vector<vector<int>> dp){
if(m == n){
result = min(result, k);
return;
}
for(int i = m; i < n; i++){
if(dp[m][i]){
k++;
backtrace(n, k, i + 1, dp);
k--;
}
}
}
public:
int minCut(string s) {
int n = s.length();
if(n <= 1)return 0;
result = n;
vector<vector<int>> dp(n);
for(int i = 0; i < n; i++){
dp[i].resize(n);
dp[i][i] = 1;
if(i > 0 && s[i] == s[i - 1])dp[i - 1][i] = 1;
}
for(int k = 3; k <= n; k++){
for(int i = 0; i <= n - k; i++){
int j = i + k - 1;
if(dp[i + 1][j - 1] && s[i] == s[j])dp[i][j] = 1;
}
}
int k = 0;
backtrace(n, k, 0, dp);
return result - 1;
}
};
动态规划的代码可以继续优化,dp矩阵求出来后可以得到一个剪枝条件。求出最长回文串的长度为 t t t,则最终结果一定大于 n − t + 1 n - t + 1 n−t+1,其中 n n n为字符串的长度。这样在第12行可以添加这个剪枝条件。这是一个小突破口,只优化了一点点,结果还是超时了。
超时代码:
class Solution {
int result, con;
void backtrace(int n, int& k, int m, vector<vector<int>> dp){
if(m == n){
result = min(result, k);
return;
}
for(int i = n - 1; i >= m; i--){
if(dp[m][i]){
k++;
if(k < con && k < result)backtrace(n, k, i + 1, dp);
k--;
}
}
}
public:
int minCut(string s) {
int n = s.length();
if(n <= 1)return 0;
result = n;
vector<vector<int>> dp(n);
for(int i = 0; i < n; i++){
dp[i].resize(n);
dp[i][i] = 1;
if(i > 0 && s[i] == s[i - 1])dp[i - 1][i] = 1;
}
int t = 0;
for(int k = 3; k <= n; k++){
for(int i = 0; i <= n - k; i++){
int j = i + k - 1;
if(dp[i + 1][j - 1] && s[i] == s[j])dp[i][j] = 1;
if(dp[i][j] && t < k)t = k;
}
}
if(dp[0][n - 1])return 0;
con = n - t + 1;
int k = 0;
backtrace(n, k, 0, dp);
return result - 1;
}
};
超时用例:
“fifgbeajcacehiicccfecbfhhgfiiecdcjjffbghdidbhbdbfbfjccgbbdcjheccfbhafehieabbdfeigbiaggchaeghaijfbjhi”
继续优化呀。剩下的这个回溯法应该可以用另一个动态规划来实现的。但是我没有想到,参考了一下题解也确实用两轮动态规划来做。
第二个动态规划使用
r
e
s
[
i
]
res[i]
res[i]表示索引i及以前范围的字符串的回文串数,因此最终答案就是
r
e
s
[
n
−
1
]
−
1
res[n - 1] - 1
res[n−1]−1。具体的思路见代码。
第一个动态规划为7-19行,第二个动态规划是21-32行。
正确解法:
class Solution {
public:
int minCut(string s) {
int n = s.length();
if(n <= 1)return 0;
vector<vector<int>> dp(n);
for(int i = 0; i < n; i++){
dp[i].resize(n);
dp[i][i] = 1;
if(i > 0 && s[i] == s[i - 1])dp[i - 1][i] = 1;
}
for(int k = 3; k <= n; k++){
for(int i = 0; i <= n - k; i++){
int j = i + k - 1;
if(dp[i + 1][j - 1] && s[i] == s[j])dp[i][j] = 1;
}
}
if(dp[0][n - 1])return 0;
vector<int> res(n);
for(int i = 0; i < n; i++){
res[i] = n;
if(dp[0][i] == 1)res[i] = 1;
else{
for(int j = 1; j <= i; j++){
if(dp[j][i]){
res[i] = min(res[i], res[j - 1] + 1);
}
}
}
}
return res[n - 1] - 1;
}
};
结果: