题目
中等
相关标签
给你一个字符串 s
,请你将s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
提示:
1 <= s.length <= 16
s
仅由小写英文字母组成
思路和解题方法一 回溯+判断
ans
是一个二维向量,用于存储最终的结果。每个元素都是一个符合要求的回文字符串组合。
path
是一个一维向量,用于存储当前的路径。它表示已经找到的符合要求的回文子串。
backtrackint
是回溯函数,用于搜索并构建符合要求的回文字符串组合。它接收两个参数:字符串s
和当前的索引index
。在
backtrackint
中,首先判断是否遍历到字符串末尾。如果是,将当前路径添加到结果ans
中,并返回。然后,从当前位置
index
开始,通过一个循环遍历字符串。变量i
表示循环的当前位置。在循环中,通过调用
ifhuimen
函数判断从当前位置index
到i
是否是回文子串。如果是回文子串,则将该子串加入当前路径path
中。然后,递归调用
backtrackint
函数,传入新的起始位置i + 1
,继续处理剩余的部分。在递归之后,需要进行回溯操作,即将当前路径
path
的最后一个元素移除,以便继续寻找其他可能的回文子串组合。最后,调用
partition
函数,通过回溯函数backtrackint
来搜索所有符合要求的回文字符串组合,并返回结果向量ans
。
复杂度
时间复杂度:
O(2^n * n)
时间复杂度
- 对于长度为 n 的字符串,最坏情况下可能有 2^(n-1) 种不同的回文子串组合。
- 在回溯过程中,我们需要遍历所有可能的组合,每个组合都需要进行 O(n) 的操作来判断是否是回文子串。
- 因此,回溯算法的时间复杂度为 O(2^n * n)。
空间复杂度
O(n^2)
空间复杂度:
- 回溯算法消耗了大量的递归栈空间,最坏情况下需要 O(n) 的空间。
- 另外,还需要额外的空间来储存存储中间结果的二维向量 ans 和存储当前路径的一维向量 path,因此空间复杂度为 O(n^2)。
c++ 代码一
cpp
class Solution {
public:
vector<vector<string>> ans; // 存储结果的二维向量
vector<string> path; // 存储当前路径的一维向量
void backtrackint(string &s, int index) {
if (index >= s.size()) { // 如果遍历到字符串末尾,将当前路径添加到结果中
ans.push_back(path);
return;
}
for (int i = index; i < s.size(); i++) { // 遍历字符串,从当前位置开始
if (ifhuimen(s, index, i)) { // 判断从当前位置到 i 是否是回文子串
string str = s.substr(index, i - index + 1); // 获取回文子串
path.push_back(str); // 将回文子串加入当前路径
} else {
continue; // 如果不是回文子串,跳过当前位置
}
backtrackint(s, i + 1); // 递归处理剩余部分
path.pop_back(); // 回溯,将当前路径的最后一个元素移除
}
}
bool ifhuimen(string &s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
if (s[i] != s[j]) {
return false; // 判断是否为回文子串
}
}
return true;
}
vector<vector<string>> partition(string s) {
ans.clear(); // 清空结果向量
path.clear(); // 清空路径向量
backtrackint(s, 0); // 回溯搜索所有回文子串的组合
return ans; // 返回结果向量
}
};
思路和解题方法二 回溯+动态规划
首先,定义了私有变量
result
、path
和isPalindrome
。result
是最终的分割结果,path
是存储已经回文的子串,isPalindrome
存储事先计算好的是否回文子串的结果。在
backtracking
函数中进行回溯操作。如果起始位置startIndex
大于等于字符串s
的大小,说明已经找到了一组分割方案,将当前的路径path
加入到结果集result
中,并返回。接下来,从
startIndex
开始循环遍历字符串s
。如果从startIndex
到当前位置i
的子串是回文子串(根据事先计算好的isPalindrome
布尔矩阵判断),则将该子串加入到路径path
中,并递归地在其后面的位置继续寻找回文子串。在回溯过程中,如果发现当前位置
i
到startIndex
的子串不是回文子串,直接跳过。如果是回文子串,继续调用backtracking
函数,并将i + 1
作为新的起始位置,寻找下一个回文子串。在每一次递归返回之前,需要将当前已经添加到路径
path
中的子串弹出,以便进行下一次回溯。
computePalindrome
函数用于计算字符串s
中的回文子串。它通过动态规划的思想,从后向前依次计算字符串中的回文子串。具体做法是,首先遍历字符串中的每个字符,将单个字符视为回文子串;然后,对于长度为2的子串,如果两个字符相等,则它们是回文子串;最后,对于长度大于2的子串,如果首尾字符相等,并且去除首尾后的子串仍然是回文子串,那么它们也是回文子串。
partition
函数是整个程序的入口。在开始分割之前,首先清空结果集result
和路径path
。然后调用computePalindrome
函数计算回文子串,并调用backtracking
函数进行回溯查找分割方案。最后,返回结果集
result
,即可得到所有的分割方案。
复杂度 和上面一样
时间复杂度:
O(2^n * n)
空间复杂度
O(n^2)
c++ 代码二
cpp
class Solution {
private:
vector<vector<string>> result; // 存储最终的分割结果
vector<string> path; // 存储已经回文的子串
vector<vector<bool>> isPalindrome; // 存储事先计算好的是否回文子串的结果
void backtracking(const string& s, int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome[startIndex][i]) { // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
} else { // 不是回文,跳过
continue;
}
backtracking(s, i + 1); // 寻找i+1为起始位置的子串
path.pop_back(); // 回溯过程,弹出本次已经添加的子串
}
}
void computePalindrome(const string& s) {
// isPalindrome[i][j] 代表 s[i:j](双边包括)是否是回文字串
isPalindrome.resize(s.size(), vector<bool>(s.size(), false)); // 根据字符串s,刷新布尔矩阵的大小
for (int i = s.size() - 1; i >= 0; i--) {
// 需要倒序计算,保证在i行时,i+1行已经计算好了
for (int j = i; j < s.size(); j++) {
if (j == i) {
isPalindrome[i][j] = true;
} else if (j - i == 1) {
isPalindrome[i][j] = (s[i] == s[j]);
} else {
isPalindrome[i][j] = (s[i] == s[j] && isPalindrome[i + 1][j - 1]);
}
}
}
}
public:
vector<vector<string>> partition(string s) {
result.clear();
path.clear();
computePalindrome(s); // 计算是否回文子串
backtracking(s, 0); // 回溯得到分割结果
return result;
}
};
觉得有用的话可以点点赞,支持一下。
如果愿意的话关注一下。会对你有更多的帮助。
每天都会不定时更新哦 >人< 。