分割回文串
题目链接
题目
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串 。返回 s 所有可能的分割方案。
例如:
输入: "aab"
输出:
[
["a","a","b"],
["aa","b"]
]
思路
-
动态规划预处理:首先使用动态规划预处理出所有的回文子串。我们使用一个二维数组
dp
,其中dp[i][j]
表示子串s[i:j+1]
是否为回文。预处理的时间复杂度为 。 -
回溯算法:在回溯过程中,通过
dp
数组快速判断子串是否为回文。我们从字符串的起始位置开始尝试分割,对于每一个位置,若子串是回文,则将其加入当前路径,并继续对剩余部分进行递归。当到达字符串末尾时,将当前路径加入结果集。
解题方法
1. 动态规划预处理
const dp = Array.from(Array(n), () => Array(n).fill(false));
for (let right = 0; right < n; right++) {
for (let left = 0; left <= right; left++) {
if (s[left] === s[right] && (right - left <= 2 || dp[left + 1][right - 1])) {
dp[left][right] = true;
}
}
}
dp
数组初始化为false
。- 双重循环填充
dp
数组。s[left] === s[right]
表示两端字符相同,如果这两个字符相邻或中间部分是回文(即dp[left + 1][right - 1]
为true
),则dp[left][right]
设为true
。
2. 回溯函数
function backTracking(start, path) {
if (start === s.length) {
result.push([...path]);
return;
}
for (let end = start; end < s.length; end++) {
if (dp[start][end]) {
path.push(s.slice(start, end + 1));
backTracking(end + 1, path);
path.pop();
}
}
}
- 回溯函数
backTracking
从起始位置start
开始分割。 - 如果
start
到end
的子串是回文(dp[start][end]
为true
),将该子串加入路径,并继续递归分割剩余部分。 - 使用
dp
表来判断子串是否为回文,避免重复计算。
代码
const partition = function(s) {
const result = [];
const path = [];
const n = s.length;
// 预处理,使用动态规划记录所有子串是否为回文
const dp = Array.from(Array(n), () => Array(n).fill(false));
for (let right = 0; right < n; right++) {
for (let left = 0; left <= right; left++) {
if (s[left] === s[right] && (right - left <= 2 || dp[left + 1][right - 1])) {
dp[left][right] = true;
}
}
}
function backTracking(start, path) {
if (start === s.length) {
result.push([...path]);
return;
}
for (let end = start; end < s.length; end++) {
if (dp[start][end]) {
path.push(s.slice(start, end + 1));
backTracking(end + 1, path);
path.pop();
}
}
}
backTracking(0, path);
return result;
};
// 测试
const s = "aab";
console.log(partition(s));
// 输出: [["a", "a", "b"], ["aa", "b"]]
复杂度分析
- 时间复杂度:,其中 是字符串的长度。预处理阶段的时间复杂度为 ,回溯阶段的时间复杂度为 。
- 空间复杂度:。主要用于存储动态规划表
dp
和递归调用栈的开销。