题目描述
竞赛积分 : 2102
思路
首先我们可以定义f[i]表示,对s[i:]字符串进行操作,可以得到的最大操作次数。因此,本题的答案是f[0]。
那么如何状态转移呢?假如有字符串长度为j,而且s[i:i+j]==s[i+j:i+2*j],那么可以实施状态转移: f[i] = f[i+j] + 1 , 意味着我可以消掉s[i:i+j]得到s[i+j:],所以s[i:]的答案为s[i+j:]+1。如果没有任何长度j满足让s消一下,那么f[i]只能为1,也就是只能操作一次(全部消掉)。
上面的代码为,从后向前枚举位置i,然后从1往极限枚举长度j,计算f数组。
问题变成了,如何快速判断s[i:i+j]==s[i+j:i+2*j]? 我们有算法LCP:
LCP
有字符串s,定义其后缀字符串i为s[i:]。那么lcp[i][j]定义为后缀字符串i和后缀字符串j的最长公共前缀。这其实也是通过dp递推得到的:如果s[i] == s[j], 那么lcp[i][j] = lcp[i+1][j+1] + 1 ; 否则 lcp[i][j] = 0 ; 这有个板子,直接背一下得了。
for ( int i = n - 1 ; i >= 0 ; i -- ) {
for (int j = n - 1 ; j >= i ; j -- ) {
if (s[i] == s[j]) {
lcp[i][j] = lcp[i+1][j+1] + 1 ;
}
}
}
因此,如果想判断s[i:i+j]==s[i+j:i+2*j],只需要判断lcp[i][i+j] >= j 即可。这意味着,后缀字符串i和后缀字符串i+j的最长公共前缀大于等于长度j,符合操作条件,允许状态转移。
代码
因为相同代码逻辑下,py会超时,所以这里放c++的代码。
class Solution {
public:
int deleteString(string s) {
const int n = s.size() ;
vector<vector<int>> lcp(n+1,vector<int>(n+1)) ;
for (int i = n - 1 ; i >= 0 ; i --) {
for (int j = n - 1 ; j >= i ; j --) {
if (s[i] == s[j]) {
lcp[i][j] = lcp[i+1][j+1] + 1 ;
}
}
}
vector<int> f(n) ;
for (int i = n- 1; i >= 0 ; i --) {
f[i] = 1 ; // 保底操作,直接删除全串
for (int j = 1 ; j < (n-i)/2 + 1 ; j ++) {
if (lcp[i][i+j] >= j){
f[i] = max(f[i] , f[i+j] + 1 ) ;
}
}
}
return f[0] ;
}
};