难度:中等
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串。返回 s 所有可能的分割方案。
示例1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例2:
输入:s = "a"
输出:[["a"]]
-
1 <= s.length <= 16
-
s 仅由小写英文字母组成
问题分析
这题让把字符串分割成一些子串,并且要保证每个子串都是回文串,这是一道经典的回溯算法问题,关于回溯算法,可以看下很早之前写的一篇文章《什么叫回溯算法,一看就会,一写就废》。
对原字符串不断的截取子串,如果子串是回文串就继续截取,如果不是回文串就跳过,最后如果都截取完了,说明截取的子串都是回文串,把截取的子串保存下来即可。可以看到子串的截取就像一个 n 叉树一样,如下图所示。

判断子串是否是回文串,我们可以预处理,先计算好哪些子串是回文串哪些不是。如果子串s[i,j]是回文串,需要两边的字符相等s[i]==s[j],并且中间的子串s[i+1,j-1]也必须是回文串。
JAVA:
public List<List<String>> partition(String s) {
List<List<String>> ans = new ArrayList<>();
int length = s.length();
// 预处理,先计算子串中哪些是回文的,哪些不是,
// 数组dp[i][j]表示子串s[i,j]是否是回文的。
boolean[][] dp = new boolean[length][length];
for (int j = 0; j < length; j++) {
for (int i = 0; i <= j; i++) {
// 如果子串s[j,i]是回文串,则两边的字符s[i]和s[j]必须相同,并且
// 中间的子串s[i+1,j-1]如果存在,也必须是回文串。
if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || dp[i + 1][j - 1]))
dp[i][j] = true;
}
}
backTrack(s, dp, 0, ans, new ArrayList<>());// 回溯算法
return ans;
}
private void backTrack(String s, boolean[][] dp, int index, List<List<String>> ans, List<String> path) {
// 边界条件判断,字符串s中的字符都访问完了
if (index >= s.length()) {
ans.add(new ArrayList<>(path));
return;
}
for (int i = index; i < s.length(); i++) {
// 如果当前截取的子串不是回文的,就跳过
if (!dp[index][i]) continue;
// 这里截取的子串s[index][i]就是回文串。
path.add(s.substring(index, i + 1));// 做出选择
backTrack(s, dp, i + 1, ans, path); // 递归
path.remove(path.size() - 1);// 撤销选择
}
}
C++:
public:
vector<vector<string>> partition(string s) {
vector<vector<string>> ans;
int length = s.length();
// 预处理,先计算子串中哪些是回文的,哪些不是,
// 数组dp[i][j]表示子串s[i,j]是否是回文的。
vector<vector<bool>> dp(length, vector<bool>(length, false));
for (int j = 0; j < length; j++) {
for (int i = 0; i <= j; i++) {
// 如果子串s[j,i]是回文串,则两边的字符s[i]和s[j]必须相同,并且
// 中间的子串s[i+1,j-1]如果存在,也必须是回文串。
if (s[i] == s[j] && (j - i <= 2 || dp[i + 1][j - 1]))
dp[i][j] = true;
}
}
vector<string> path{};
backTrack(s, dp, 0, ans, path);// 回溯算法
return ans;
}
void backTrack(string &s, vector<vector<bool>> &dp, int index,
vector<vector<string>> &ans, vector<string> &path) {
// 边界条件判断,字符串s中的字符都访问完了
if (index >= s.length()) {
ans.push_back(path);
return;
}
for (int i = index; i < s.length(); i++) {
// 如果当前截取的子串不是回文的,就跳过
if (!dp[index][i])
continue;
// 这里截取的子串s[index][i]就是回文串。
path.push_back(s.substr(index, i - index + 1));// 做出选择
backTrack(s, dp, i + 1, ans, path); // 递归
path.pop_back();// 撤销选择
}
}
Python:
def partition(self, s: str) -> List[List[str]]:
def backTrack(index):
# 边界条件判断,字符串s中的字符都访问完了
if index >= length:
ans.append(path[:])
return
for i in range(index, length):
# 如果当前截取的子串不是回文的,就跳过
if not dp[index][i]:
continue
# 这里截取的子串s[index][i]就是回文串。
path.append(s[index: i + 1]) # 做出选择
backTrack(i + 1) # 递归
path.pop() # 撤销选择
ans = []
path = []
length = len(s)
# 预处理,先计算子串中哪些是回文的,哪些不是,
# 数组dp[i][j]表示子串s[i,j]是否是回文的。
dp = [[False] * length for _ in range(length)]
for j in range(length):
for i in range(j + 1):
# 如果子串s[i,j]是回文串,则两边的字符s[i]和s[j]必须相同,并且
# 中间的子串s[i+1,j-1]如果存在,也必须是回文串。
if s[i] == s[j] and (j - i <= 2 or dp[i + 1][j - 1]):
dp[i][j] = True
backTrack(0) # 回溯算法
return ans