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.
题意: 给定一字符串,求最少切的次数,是的切割所得的所有字串都为回文串!
最开始想的是递归 + dp,dp[i j] 表示从s[i j] 需要的最少的切割的次数,
则dp[i j] = min{dp[i k] + dp[k + 1 j] + 1 | k = [i ,j), i j = [0 s.size()]};
此解法,i j扫描时为O(n ^2) ,同时对k的扫描为O(n),总体的复杂度为O(n ^3),
当数据长度为1400+是超时。于是想着压缩dp,dp[i] 表示s[0 i]需要的最少切割次数,
则dp[i] = min{dp[k] + 1 | k = [0 i - 1] && s[k i]为回文串};
当程序进行到i时, s[0 i-1]已经考虑了所有的最优切法,加入s[i]时,需要考虑的增加的可能的切法
即为{s[0 k] ,s[k + 1, i] | s[k + 1 i]为回文串};
因为当加入i时,切法要么是在原来的基础上+1 ,要么是减少原来的切割次数,
而能减少切割次数的情况只能是s[k]与 s[i]之前的字串组成会回文串,这里+1的情况实际上是k = i的特殊情况
最开始当计算dp[i]时,我的解法是从0 --i 扫描k计算k[i]是否是回文串,然而还是会超时,
此解法时间复杂度为O(n^2),其实在判断s[k i]是否是回文串时也是O(n),复杂度也接近O(n^3),超时!
因此,对求回文串的解法还要优化,用symt[i](symt为 vector<vector<int>>)表示 i与之前所有下标k,使得s[k i]为回文串,
则symt[i] = {i,L,s[i- 1][k] - 1 | L= (i - 1 && s[L - 1]==s[i]), k 满足s[s[i - 1][k] - 1 ] == s[i]},
因为当s[i j]为回文串时,s[i+ 1, j - 1]一定为回文串,因为k 属于{0 ...i- 1},所有还需考虑
s[i-1,i]和s[i,i]!次解法最坏为O(n^3),但实际情况中并不是有这么多回文子串!
超时代码:
const int MAX = 5000+ 5;
int dp[MAX][MAX];
int c= 0,us= 0;
class Solution {
public:
string s;
int minCut(string s) {
if(!s.size())return 0;
this->s = s;
int len = s.size();
for(int i = 0; i < len; i++)
for(int j = 0; j < len; j++)
dp[i][j] = -1;
return cut(0,len - 1);
}
int cut(int l, int r)
{c++;
if(dp[l][r] != -1)return dp[l][r];
us++;
int midlen = (r - l + 1) / 2 ,i;
for(i = 0; i < midlen; i++)
{
if(s[l + i] != s[r - i])break;
}
if(i == midlen)
{
dp[l][r] = 0;
return 0;
}
int rst = r - l;
for(int k = l; k < r; k++)
{
int lcut = cut(l, k);
int rcut = cut(k + 1, r);
rst = min(rst, lcut + rcut + 1);
}
dp[l][r] = rst;
return rst;
}
};
AC代码:
const int MAX = 20000+ 5;
int dp[MAX];
class Solution {
public:
string s;
void generate_pal_vector(vector<vector<int> > &symt)
{
vector<int> v;
for(int i = 0; i < s.size(); i++)
{
v.clear();
v.push_back(i);//单个字符s[i]也是回文串
//有 i- 1操作的注意左边界
if(i > 0 && s[i] == s[i - 1])v.push_back(i - 1);
if(i > 0)
for(int k = 0; k < symt[i - 1].size(); k++)
{
if(symt[i - 1][k] > 0 && s[i] == s[symt[i - 1][k] - 1])
v.push_back(symt[i - 1][k] - 1);
}
symt.push_back(v);
}
}
int minCut(string s) {
if(!s.size())return 0;
this->s = s;
int len = s.size();
vector<vector<int> > symt;
generate_pal_vector(symt);
dp[0] = -1;
//计算dp[i]的最优解
for(int i = 1; i <= len; i++)
{
//最坏为 i - 1种切法
int rst = i - 1;
//遍历i之前与i组成回文串的所有下标k,更新最小值
for(int k = 0; k < symt[i - 1].size(); k++)
{
int idx = symt[i - 1][k];
if(s[i - 1] == s[idx])
{
rst = min(rst, dp[idx] + 1);
}
}
dp[i] = rst;
}
return dp[len];
}
};