这是一个非常经典的**回溯算法(Backtracking)**题目,通常对应 LeetCode 131. Palindrome Partitioning(分割回文串)。
算法题解:分割回文串 (LeetCode 131)
在处理字符串分割问题时,我们经常需要找到满足特定条件(如"必须是回文")的所有分割方案。这类问题是回溯算法的典型应用场景。
今天我们来拆解一种结构非常清晰的解法:预处理 + DFS回溯。
核心思路
这个问题的难点在于:我们需要把字符串切成若干块,而且每一块都必须是回文串。
我们的策略分为两步走:
-
预处理 (Preprocessing):先算好哪些子串是回文串,存进一个表格(Cache)里。这样在后面搜索时,查表就知道是不是回文,不用重复计算。
-
回溯搜索 (Backtracking):利用深度优先搜索(DFS)尝试在每一个位置"切一刀"。如果切出来的这一段是回文,就继续往下切;否则这就不是一条可行的路。
逻辑图解
假设输入字符串 s = "aab":
-
预处理阶段 :我们生成一个二维数组
cache。-
s[0:0] ("a")-> True -
s[1:1] ("a")-> True -
s[0:1] ("aa")-> True -
s[2:2] ("b")-> True -
... 其他为 False
-
-
DFS 搜索树:
Plaintext
dfs(0) -> 剩余 "aab" / \ 切 "a" (是回文) 切 "aa" (是回文) / \ dfs(1) -> 剩 "ab" dfs(2) -> 剩 "b" / \ 切 "a" (是回文) 切 "b" (是回文) / \ dfs(2) -> 剩 "b" dfs(3) -> 结束 -> 收集 ["aa", "b"] / 切 "b" (是回文) / dfs(3) -> 结束 -> 收集 ["a", "a", "b"]
代码详解
1. 基础工具:判断回文 (isPan)
这是一个标准的双指针写法。
Python
def isPan(self, s):
left, right = 0, len(s)-1
while left <= right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
📝 注:虽然这部分很简单,但被频繁调用。
2. 核心逻辑:partition 主函数
第一步:建立缓存 (Cache)
为了避免在 DFS 过程中反复检查同一个子串是不是回文,代码先暴力计算了所有可能的子串。
Python
# 初始化全为 False 的二维数组
cache = [[False for _ in range (len(s))] for _ in range (len(s))]
# 填表:检查从 i 到 j 的子串 s[i:j+1] 是否为回文
for i in range(len(s)):
for j in range(i, len(s)):
if self.isPan(s[i:j+1]):
cache[i][j] = True
第二步:DFS 回溯
这是算法的灵魂。我们需要维护两个变量:
-
k: 当前处理到的字符串起始索引。 -
path: 当前已经切分好的回文串列表。
Python
res, path = [], []
def dfs(k):
# 🛑 终止条件:如果我们已经处理到了字符串末尾
if k >= len(s):
res.append(path.copy()) # 注意要拷贝一份 path,因为 path 是引用的
return
# 🔄 单层搜索逻辑:尝试从 k 切到 i
for i in range(k, len(s)):
# 查表:如果 s[k:i+1] 是回文
if cache[k][i]:
path.append(s[k:i+1]) # 1. 做选择:加入路径
dfs(i+1) # 2. 递归:处理剩下的部分
path.pop() # 3. 撤销选择:回溯,尝试下一种切法
复杂度分析
-
时间复杂度:
- 预处理部分 :双重循环遍历所有子串是 n^2,内部调用的
isPan是 O(N),所以预处理是 O(N^2)。
- 预处理部分 :双重循环遍历所有子串是 n^2,内部调用的
-
空间复杂度:
-
cache数组占用 O(N^2)。 -
递归调用栈最大深度为 O(N)。
-
总结
这段代码通过空间换时间 的思想(使用 cache),让 DFS 过程中的判断变得非常快(O(1))。虽然预处理部分还有优化的空间(可以使用中心扩展法或动态规划将预处理降为 O(N^2)),但作为一道面试题解,这种结构化、模块化的写法非常利于展示思路。
关键点回顾:
-
双指针判断回文。
-
二维数组缓存结果。
-
Backtracking 标准模板:做选择 -> 递归 -> 撤销选择。
希望这篇解析能帮你搞定分割回文串!