給定字符串 s,將其分割成若干子串,使每個子串都是回文。返回所有可能的分割方案。
例:s = "aab" → [["aa","b"], ["a","a","b"]]
dfs(start, path),start 為當前待分割起始索引,path 為已選子串列表start == s.length(),將 path 加入結果集for end = start+1 .. s.length()
sub = s.substring(start, end)sub 是回文:path.add(sub),遞歸 dfs(end, path),回溯 path.removeLast()預處理 isPal[i][j] 表示 s[i..j] 是否回文,O(N²) 預處理,O(1) 查詢。
DP 遞推:isPal[i][j] = (s[i]==s[j]) && (j-i<2 || isPal[i+1][j-1])
class Solution {
List> res = new ArrayList<>();
boolean[][] isPal;
public List> partition(String s) {
int n = s.length();
isPal = new boolean[n][n];
for (int i = n-1; i >= 0; --i)
for (int j = i; j < n; ++j)
isPal[i][j] = (s.charAt(i) == s.charAt(j)) && (j-i < 2 || isPal[i+1][j-1]);
backtrack(s, 0, new ArrayList<>());
return res;
}
void backtrack(String s, int start, List path) {
if (start == s.length()) {
res.add(new ArrayList<>(path));
return;
}
for (int end = start+1; end <= s.length(); ++end) {
if (isPal[start][end-1]) {
path.add(s.substring(start, end));
backtrack(s, end, path);
path.remove(path.size()-1);
}
}
}
}
class Solution {
public:
vector> partition(string s) {
int n = s.size();
vector> isPal(n, vector(n));
for (int i = n-1; i >= 0; --i)
for (int j = i; j < n; ++j)
isPal[i][j] = (s[i]==s[j]) && (j-i<2 || isPal[i+1][j-1]);
vector> res;
vector path;
function dfs = [&](int start) {
if (start == n) { res.push_back(path); return; }
for (int end = start+1; end <= n; ++end) {
if (isPal[start][end-1]) {
path.push_back(s.substr(start, end-start));
dfs(end);
path.pop_back();
}
}
};
dfs(0);
return res;
}
};
O(N²) 時間、O(N²) 空間2^(N-1),每個方案複製 O(N) → O(N × 2^N)N ≤ 16(LeetCode 限制)完全可行若 N 很大或內存受限,可在回溯中雙指針判斷回文 O(len),總複雜度仍為指數級但常數較小。
經典回溯模板題。關鍵點:預處理回文表加速判斷、遞歸枚舉分割點、路徑回溯。代碼結構清晰,Java/C++ 實現均簡潔。掌握此模板可解決所有「字符串分割 + 子串性質約束」問題。