一、题目
给你一个字符串s,请你将s分割成一些子串,使每个子串都是
回文串
。返回s所有可能的分割方案。
回文串是正着读和反着读都一样的字符串。
提示:
1)1 <= s.length <= 16
2)s 仅由小写英文字母组成
二、解题思路
这道题目要求将给定的字符串分割成若干子串,每个子串都必须是回文形式,并询问有多少种不同的分割方案。我们以示例1中的字符串"aab"为例,详细说明如何进行分割:
1. 首先,尝试截取1位字符"a",然后将剩余的"ab"作为一个新的字符串继续进行判断。
2. 接着,尝试截取2位字符"aa",然后将剩余的"b"作为一个新的字符串继续进行判断。
3. 最后,尝试截取3位字符"aab",此时整个字符串已被完全截取。
具体的分割过程可以通过下图来直观展示。
一看就知道,这个问题本质上是一个n叉树的深度优先搜索(DFS)遍历。在这个树中,从根节点到叶子节点的路径代表了字符串s的一种分割方式。我们需要验证这些路径上的每个子串是否都是回文串。一旦发现某个子串不是回文,我们就可以立即停止进一步的搜索。只有当从根节点到叶子节点的每一步都满足回文条件时,我们才算找到了一种有效的分割方案。
然而,题目要求我们不仅要找到分割方案,还要返回具体的分割结果。因此,我们可以采用回溯算法来解决这个问题。回溯算法允许我们在尝试各种可能的分割方式时,记录并返回有效的结果。
三、代码实现
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 判断字符串从[left,right]的子串是否是回文的
bool isPalindrome(const string& str, int left, int right) {
while (left < right) {
if (str[left++] != str[right--])
return false;
}
return true;
}
// 回溯函数
void backTrack(const string& s, int index, vector<vector<string>>& res, vector<string>& cur) {
// 边界条件判断,如果字符串s中的字符都访问完了(类似于到叶子节点了),就停止查找,
// 然后这个分支的所有元素加入到集合res中
if (index >= s.length()) {
res.push_back(cur);
return;
}
for (int i = index; i < s.length(); i++) {
// 如果当前截取的子串不是回文的,就跳过
if (!isPalindrome(s, index, i))
continue;
// 做出选择
cur.push_back(s.substr(index, i - index + 1));
// 递归
backTrack(s, i + 1, res, cur);
// 撤销选择
cur.pop_back();
}
}
// 主函数
vector<vector<string>> partition(const string& s) {
// 最终要返回的结果
vector<vector<string>> res;
vector<string> cur;
backTrack(s, 0, res, cur);
return res;
}
int main() {
string s = "aab";
vector<vector<string>> result = partition(s);
// 打印结果
for (const auto& partition : result) {
cout << "[ ";
for (const auto& str : partition) {
cout << str << " ";
}
cout << "]" << endl;
}
return 0;
}
代码优化
在上述方法中,每次截取子串时都需要判断该子串是否为回文,这可能会导致重复计算。为了优化这一过程,我们可以预先计算并存储字符串
s
中所有可能子串的回文状态。具体实现时,我们使用一个二维布尔数组
dp
,其中
dp[i][j]
表示字符串从下标
i
到
j
的子串是否为回文。这样,在后续的回溯过程中,我们可以直接查询
dp
数组来判断子串是否为回文,从而避免重复计算。下面是优化后的代码:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 回溯函数
void backTrack(const string& s, const vector<vector<bool>>& dp, int index, vector<vector<string>>& res, vector<string>& cur) {
// 边界条件判断,如果字符串s中的字符都访问完了(类似于到叶子节点了),就停止查找,
// 然后这个分支的所有元素加入到集合res中
if (index >= s.length()) {
res.push_back(cur);
return;
}
for (int i = index; i < s.length(); i++) {
// 如果当前截取的子串不是回文的,就跳过
if (!dp[index][i])
continue;
// 做出选择
cur.push_back(s.substr(index, i - index + 1));
// 递归
backTrack(s, dp, i + 1, res, cur);
// 撤销选择
cur.pop_back();
}
}
// 主函数
vector<vector<string>> partition(const string& s) {
// 最终要返回的结果
vector<vector<string>> res;
int length = s.length();
// 下面先计算子串中哪些是回文的,哪些不是
// 数组dp[i][j]表示字符串s下标[i,j]的子串是否是回文的
vector<vector<bool>> dp(length, vector<bool>(length, false));
for (int i = 0; i < length; i++) {
for (int j = 0; j <= i; j++) {
if (s[i] == s[j] && (i - j <= 2 || dp[j + 1][i - 1])) {
dp[j][i] = true;
}
}
}
vector<string> cur;
backTrack(s, dp, 0, res, cur);
return res;
}
int main() {
string s = "aab";
vector<vector<string>> result = partition(s);
// 打印结果
for (const auto& partition : result) {
cout << "[ ";
for (const auto& str : partition) {
cout << str << " ";
}
cout << "]" << endl;
}
return 0;
}