0%

LeetCode-131-分割回文串

LeetCode 131 分割回文串

131.分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

暴力穷举的回溯

对于子串的划分,初始仅选取 cur 处的元素作为子串 str ,判断生成的子串是否是回文,若是回文则将子串加入到 tmp 数组中,cur+1 开始递归,在递归返回后,将 tmp 中的值pop完成回溯,在下一步循环中将 s[i] 加入到 str 中作为新的字串,再做判断和递归回溯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
vector<string> tmp;
vector<vector<string>> res;
bool isSuit(string a){
for(int i=0;i<a.size()/2+1;i++){
if(a[i]!=a[a.size()-1-i]) return false;
}
return true;
}
void DFS(string s,int cur){
if(cur==s.size()){ // 完成划分
res.push_back(tmp);
return;
}
string str;
for(int i=cur;i<s.size();i++){
str+=s[i];
if(isSuit(str)){ // 判断是否为回文
tmp.push_back(str);
DFS(s,i+1);
tmp.pop_back();
}
}
}
vector<vector<string>> partition(string s) {
DFS(s,0);
return res;
}
};

使用动态规划优化回文的判断

在回文的判断中,使用的是最简单的双指针移动的方式进行的。这样的判断是存在重复操作的。例如给定字符串”abcde”, 在已知”bcd”不是回文字串时, 不再需要去双指针操作”abcde”而可以直接判定它一定不是回文字串。(也就是对于字符串的中间不是回文,那么整体就一定不是回文)

判断回文的充分必要条件:给定一个字符串s, 长度为n, 它成为回文字串的充分必要条件是 s[0] == s[n-1]s[1:n-1] 是回文字串。

此处使用一个二维数组 f[i][j] 表示字符串 s 中从 ij 的子串是否是回文。那么首先是建立状态转移方程:

此处的状态转移方程:当 $i\ge j$ 时,>时不存在子串,=时为单个字符,所以必定为true。其他情况则是根据充要条件设置,两端满足并且中间满足。

那么要实现动态规划的过程:首先初始化处理的是 $i\ge j$ 的情形,将相应的元素置为true,然后对于 i 在计算时需要使用 i+1 的值,j 在计算时需要使用 j-1 的值。一个反向计算,一个正向计算。(并且此处是只需要计算 $i<j$ 的情形的。)

1
2
3
4
5
6
7
vector<vector<bool>> res;
res.resize(s.size(),vector<bool>(s.size(),true));
for(int i=s.size()-1;i>-1;i--){
for(int j=i+1;j<s.size();j++){// 只需要计算小于的情形{
res[i][j]=(s[i] == s[j]) && res[i + 1][j - 1];
}
}

在此基础上改经的结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
vector<string> tmp;
vector<vector<string>> res;
vector<vector<bool>> ispalindrome;
void DFS(string s,int cur){
if(cur>=s.size()){ // 完成划分
res.push_back(tmp);
return;
}
for(int i=cur;i<s.size();i++){
if(ispalindrome[cur][i]){ // 判断
tmp.push_back(s.substr(cur,i-cur+1)); // 函数原型为: s.substr(pos, len)
DFS(s,i+1);
tmp.pop_back();
}
}
}
vector<vector<string>> partition(string s) {
int n=s.size();
ispalindrome.resize(n,vector<bool>(n,true));
for(int i=n-1;i>-1;i--)
for(int j=i+1;j<n;j++)
ispalindrome[i][j]=(s[i]==s[j])&&ispalindrome[i+1][j-1];
DFS(s,0);
return res;
}
};

将动态规划的数组改变为动态生成

也就是将动态规划的过程改为所谓的记忆化搜索。对于回文判断数组 ispalindrome 的值不进行预先生成,而是采用在判断的过程中进行生成,生成的方式与之前相同。 f[i][j] = 0 表示未搜索,1 表示是回文串,-1 表示不是回文串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
private:
vector<vector<int>> f;
vector<vector<string>> ret;
vector<string> ans;
int n;

public:
void dfs(const string& s, int i) {
if (i == n) {
ret.push_back(ans);
return;
}
for (int j = i; j < n; ++j) {
if (isPalindrome(s, i, j) == 1) {
ans.push_back(s.substr(i, j - i + 1));
dfs(s, j + 1);
ans.pop_back();
}
}
}

// 记忆化搜索中,f[i][j] = 0 表示未搜索,1 表示是回文串,-1 表示不是回文串
int isPalindrome(const string& s, int i, int j) {
if (f[i][j]) {
return f[i][j];
}
if (i >= j) {
return f[i][j] = 1;
}
return f[i][j] = (s[i] == s[j] ? isPalindrome(s, i + 1, j - 1) : -1);
}

vector<vector<string>> partition(string s) {
n = s.size();
f.assign(n, vector<int>(n));

dfs(s, 0);
return ret;
}
};

小结

对于子串分割问题,本质上是在不断增长子串的长度,初始长度为1,然后向下递归,回溯后,将新的子串长度+1,再向下递归。

-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道