携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情
题目
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2
输入:s = "a"
输出:[["a"]]
提示
1 <= s.length <= 16s仅由小写英文字母组成
题解
思路
分割回文串,要求返回所有可能的分割方案,那么首先肯定是要枚举所有可能的分割方案,那么当设计到所有可能的分割方案的时候,想到之前做到的一些全排列啊、子集之类的问题,不难想到要用到回溯的方法,通过典型的回溯模板去实现递归枚举出所有可能的分割方案,其实这题真正的回溯思想体现在哪里呢,回到题意,题目中的意思是将原来的字符串分割成对应所有的子串,并且要求每一个子串都是回文,这不就很像全排列或者子集问题嘛,如果当前分割的这一块符合回文,那么先把它放到临时数组中,然后下一层递归调用去判断,如果不符合,就返回,并弹出,如果能够一直递归到最后一个,那么说明这样分割是符合题意的,是可行的。
其实,理解题意是关键!
那么枚举出所有可能的方案之后,本题最关键的就是判断是否为回文串了,那么对于单一的判断是否为回文串首先想到的就是双指针,但是对于每个可能的情况都用双指针循环一次判断整个过程下来的效率不是很高的,所有我们去思考有没有什么办法能够实现线性时间下判断是否为回文,考虑到重复运算,我们可以利用动态规划预处理的特点来将所有区间是否是回文串保存预处理到一个数组中,利用空间换时间的效率思想可以实现线性时间下的判断。
注意在用动态规划预处理的时候为了确保动态规划数组能实时更新,利用记忆化递归的特点,我们依次右移右指针,让左指针每次从 0 开始递增到小于等于右指针,主要是考虑到动态转移方程是要借助 i+1 和 j-1 的答案作为判断,所以必须保证当前判断的时候,i+1 和 j-1 的答案已经被更新过了,从这出发,可以写出正确的 dp 预处理。
代码
class Solution {
public:
void dfs(const string& s,int index) {
if(index==n) {
res.push_back(temp);
return;
}
for(int i=index;i<n;i++) {
if(ispalindrome[index][i]) {
temp.push_back(s.substr(index,i-index+1));
dfs(s,i+1);
temp.pop_back();
}
}
}
vector<vector<string>> partition(string s) {
n=s.size();
ispalindrome.assign(n,vector<int>(n,true));
for(int j=0;j<n;j++){
for(int i=0;i<=j;i++){
if(j==i)continue;
if(i+1 >=n || j-1<0)continue;
ispalindrome[i][j]=(s[i]==s[j]) && ispalindrome[i+1][j-1];
}
}
dfs(s,0);
return res;
}
private:
int n=0;
vector<vector<int>> ispalindrome;
vector<vector<string>> res;
vector<string> temp;
};
结语
业精于勤,荒于嬉;行成于思,毁于随。