Leetcode131题解 -Python-回溯+cache缓存

这是一个非常经典的**回溯算法(Backtracking)**题目,通常对应 LeetCode 131. Palindrome Partitioning(分割回文串)。


算法题解:分割回文串 (LeetCode 131)

在处理字符串分割问题时,我们经常需要找到满足特定条件(如"必须是回文")的所有分割方案。这类问题是回溯算法的典型应用场景。

今天我们来拆解一种结构非常清晰的解法:预处理 + DFS回溯

核心思路

这个问题的难点在于:我们需要把字符串切成若干块,而且每一块都必须是回文串。

我们的策略分为两步走:

  1. 预处理 (Preprocessing):先算好哪些子串是回文串,存进一个表格(Cache)里。这样在后面搜索时,查表就知道是不是回文,不用重复计算。

  2. 回溯搜索 (Backtracking):利用深度优先搜索(DFS)尝试在每一个位置"切一刀"。如果切出来的这一段是回文,就继续往下切;否则这就不是一条可行的路。


逻辑图解

假设输入字符串 s = "aab"

  1. 预处理阶段 :我们生成一个二维数组 cache

    • s[0:0] ("a") -> True

    • s[1:1] ("a") -> True

    • s[0:1] ("aa") -> True

    • s[2:2] ("b") -> True

    • ... 其他为 False

  2. 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)。
  • 空间复杂度

    • cache 数组占用 O(N^2)。

    • 递归调用栈最大深度为 O(N)。


总结

这段代码通过空间换时间 的思想(使用 cache),让 DFS 过程中的判断变得非常快(O(1))。虽然预处理部分还有优化的空间(可以使用中心扩展法或动态规划将预处理降为 O(N^2)),但作为一道面试题解,这种结构化、模块化的写法非常利于展示思路。

关键点回顾:

  1. 双指针判断回文。

  2. 二维数组缓存结果。

  3. Backtracking 标准模板:做选择 -> 递归 -> 撤销选择。

希望这篇解析能帮你搞定分割回文串!

相关推荐
s1hiyu4 小时前
C++动态链接库开发
开发语言·c++·算法
(❁´◡`❁)Jimmy(❁´◡`❁)4 小时前
CF2188 C. Restricted Sorting
c语言·开发语言·算法
星火开发设计4 小时前
C++ 预处理指令:#include、#define 与条件编译
java·开发语言·c++·学习·算法·知识
许泽宇的技术分享4 小时前
第 1 章:认识 Claude Code
开发语言·人工智能·python
AIFQuant4 小时前
如何利用免费股票 API 构建量化交易策略:实战分享
开发语言·python·websocket·金融·restful
Hx_Ma164 小时前
SpringMVC返回值
java·开发语言·servlet
独自破碎E4 小时前
【滑动窗口+字符计数数组】LCR_014_字符串的排列
android·java·开发语言
布局呆星4 小时前
SQLite数据库的介绍与使用
数据库·python
2401_838472514 小时前
用Python和Twilio构建短信通知系统
jvm·数据库·python
2601_949480064 小时前
【无标题】
开发语言·前端·javascript