当前位置: 首页 > news >正文

LeetCode第132题_分割回文串II

LeetCode 第132题:分割回文串 II

题目描述

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。

返回符合要求的 最少分割次数

难度

困难

题目链接

点击在LeetCode中查看题目

示例

示例 1:

输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。

示例 2:

输入:s = "a"
输出:0

示例 3:

输入:s = "ab"
输出:1

提示

  • 1 <= s.length <= 2000
  • s 仅由小写英文字母组成

解题思路

方法一:动态规划

这道题是"分割回文串"的进阶版,要求找到最少的分割次数,使得分割后的每个子串都是回文串。我们可以使用动态规划来解决这个问题。

关键点:

  • 使用动态规划预处理判断子串是否为回文串
  • 使用动态规划计算最少分割次数

具体步骤:

  1. 预处理判断子串是否为回文串
    • 定义isPalindrome[i][j]表示s[i…j]是否为回文串
    • 状态转移方程:isPalindrome[i][j] = (s[i] == s[j]) && (j - i <= 1 || isPalindrome[i+1][j-1])
  2. 计算最少分割次数
    • 定义dp[i]表示s[0…i]的最少分割次数
    • 初始化dp[i] = i(最坏情况下,每个字符都是一个回文串,需要i次分割)
    • 状态转移方程:
      • 如果s[0…i]是回文串,则dp[i] = 0(不需要分割)
      • 否则,遍历j从0到i-1,如果s[j+1…i]是回文串,则dp[i] = min(dp[i], dp[j] + 1)
  3. 返回dp[n-1],其中n是字符串的长度

时间复杂度:O(n2),其中n是字符串的长度。需要O(n2)的时间预处理回文串,以及O(n^2)的时间计算最少分割次数。
空间复杂度:O(n2),需要O(n2)的空间存储isPalindrome数组,以及O(n)的空间存储dp数组。

方法二:优化的动态规划

我们可以对方法一进行优化,减少空间复杂度。

关键点:

  • 使用中心扩展法判断回文串,避免使用O(n^2)的空间
  • 使用动态规划计算最少分割次数

具体步骤:

  1. 定义dp[i]表示s[0…i]的最少分割次数
  2. 初始化dp[i] = i(最坏情况下,每个字符都是一个回文串,需要i次分割)
  3. 对于每个位置i,以i为中心向两边扩展,找到所有以i为中心的回文串
    • 对于奇数长度的回文串,从i向两边扩展
    • 对于偶数长度的回文串,从i和i+1向两边扩展
  4. 对于每个找到的回文串s[j…i],更新dp[i] = min(dp[i], dp[j-1] + 1)
  5. 如果s[0…i]是回文串,则dp[i] = 0
  6. 返回dp[n-1]

时间复杂度:O(n^2),其中n是字符串的长度。
空间复杂度:O(n),只需要O(n)的空间存储dp数组。

图解思路

动态规划过程分析表

以示例1为例:s = “aab”

预处理回文串
isPalindrome[i][j]j=0j=1j=2
i=0truetruefalse
i=1-truefalse
i=2--true
计算最少分割次数
is[0…i]dp[i]初始值计算过程最终dp[i]说明
0“a”0s[0…0]是回文串,dp[0] = 00单个字符是回文串,不需要分割
1“aa”1s[0…1]是回文串,dp[1] = 00"aa"是回文串,不需要分割
2“aab”2s[0…2]不是回文串
s[1…2]不是回文串,dp[2] = min(dp[2], dp[0] + 1) = min(2, 0 + 1) = 1
s[2…2]是回文串,dp[2] = min(dp[2], dp[1] + 1) = min(1, 0 + 1) = 1
1"aab"需要分割一次

中心扩展法分析表

中心位置扩展类型找到的回文串更新dp[i]说明
0奇数长度“a”dp[0] = 0单个字符是回文串
0偶数长度“aa”dp[1] = 0"aa"是回文串
1奇数长度“a”dp[1] = min(dp[1], dp[0] + 1) = 0dp[1]已经是0,不更新
1偶数长度“ab”-"ab"不是回文串,不更新
2奇数长度“b”dp[2] = min(dp[2], dp[1] + 1) = min(2, 0 + 1) = 1更新dp[2] = 1

代码实现

C# 实现

public class Solution {
    public int MinCut(string s) {
        int n = s.Length;
        
        // 预处理回文串
        bool[,] isPalindrome = new bool[n, n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= i; j++) {
                if (s[j] == s[i] && (i - j <= 1 || isPalindrome[j + 1, i - 1])) {
                    isPalindrome[j, i] = true;
                }
            }
        }
        
        // 计算最少分割次数
        int[] dp = new int[n];
        for (int i = 0; i < n; i++) {
            dp[i] = i; // 最坏情况下,需要i次分割
            
            if (isPalindrome[0, i]) {
                dp[i] = 0; // 如果s[0...i]是回文串,不需要分割
                continue;
            }
            
            for (int j = 0; j < i; j++) {
                if (isPalindrome[j + 1, i]) {
                    dp[i] = Math.Min(dp[i], dp[j] + 1);
                }
            }
        }
        
        return dp[n - 1];
    }
}

Python 实现

class Solution:
    def minCut(self, s: str) -> int:
        n = len(s)
        
        # 预处理回文串
        is_palindrome = [[False] * n for _ in range(n)]
        for i in range(n):
            for j in range(i + 1):
                if s[j] == s[i] and (i - j <= 1 or is_palindrome[j + 1][i - 1]):
                    is_palindrome[j][i] = True
        
        # 计算最少分割次数
        dp = list(range(n))  # 初始化dp[i] = i
        for i in range(n):
            if is_palindrome[0][i]:
                dp[i] = 0  # 如果s[0...i]是回文串,不需要分割
                continue
            
            for j in range(i):
                if is_palindrome[j + 1][i]:
                    dp[i] = min(dp[i], dp[j] + 1)
        
        return dp[n - 1]

C++ 实现

class Solution {
public:
    int minCut(string s) {
        int n = s.length();
        
        // 预处理回文串
        vector<vector<bool>> isPalindrome(n, vector<bool>(n, false));
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= i; j++) {
                if (s[j] == s[i] && (i - j <= 1 || isPalindrome[j + 1][i - 1])) {
                    isPalindrome[j][i] = true;
                }
            }
        }
        
        // 计算最少分割次数
        vector<int> dp(n);
        for (int i = 0; i < n; i++) {
            dp[i] = i; // 最坏情况下,需要i次分割
            
            if (isPalindrome[0][i]) {
                dp[i] = 0; // 如果s[0...i]是回文串,不需要分割
                continue;
            }
            
            for (int j = 0; j < i; j++) {
                if (isPalindrome[j + 1][i]) {
                    dp[i] = min(dp[i], dp[j] + 1);
                }
            }
        }
        
        return dp[n - 1];
    }
};

执行结果

C# 实现

  • 执行用时:92 ms
  • 内存消耗:39.8 MB

Python 实现

  • 执行用时:1024 ms
  • 内存消耗:31.2 MB

C++ 实现

  • 执行用时:56 ms
  • 内存消耗:8.7 MB

性能对比

语言执行用时内存消耗特点
C#92 ms39.8 MB执行速度适中,内存消耗较高
Python1024 ms31.2 MB执行速度较慢,内存消耗适中
C++56 ms8.7 MB执行速度最快,内存消耗最低

代码亮点

  1. 🎯 使用动态规划预处理回文串,避免重复计算
  2. 💡 巧妙利用dp数组存储最少分割次数,状态转移清晰
  3. 🔍 优化判断条件,当s[0…i]是回文串时直接设置dp[i] = 0
  4. 🎨 代码结构清晰,逻辑简单易懂

常见错误分析

  1. 🚫 预处理回文串的状态转移方程错误,导致判断回文串不正确
  2. 🚫 初始化dp数组不正确,影响最终结果
  3. 🚫 没有考虑s[0…i]是回文串的特殊情况,导致计算错误
  4. 🚫 遍历顺序错误,导致状态转移不正确

解法对比

解法时间复杂度空间复杂度优点缺点
动态规划(预处理回文串)O(n^2)O(n^2)实现简单,思路清晰空间复杂度较高
优化的动态规划(中心扩展法)O(n^2)O(n)空间复杂度较低实现稍复杂
回溯(暴力枚举)O(2^n)O(n)思路直观时间复杂度高,会超时

相关题目

  • LeetCode 131. 分割回文串 - 中等
  • LeetCode 5. 最长回文子串 - 中等
  • LeetCode 647. 回文子串 - 中等
  • LeetCode 1745. 回文串分割 IV - 困难
  • LeetCode 1278. 分割回文串 III - 困难

相关文章:

  • 手机中的type-C是如何防水的呢?
  • R语言使用ggplot2作图
  • RabbitMQ详解,RabbitMQ是什么?架构是怎样的?
  • ffmpeg音视频处理流程
  • vue 3 从零开始到掌握
  • 《R 数据框》
  • 检测链表是否有环, 动画演示, Floyd判圈算法扩展应用
  • stable diffusion 量化加速点
  • 2025-04-06 Unity Editor 2 —— GUILayout
  • MySQL【sql之DML】
  • mac安装低版本node
  • 使用注解开发springMVC
  • 华东师范​地面机器人融合空中无人机视角的具身导航!KiteRunner:语言驱动的户外环境合作式局部-全局导航策略
  • 结构化数据库和非结构化数据库的区别是什么
  • 轨迹速度聚类 实战 速度平滑
  • 大模型(二)神经网络
  • Autosar应用层开发基础——Arxml制作
  • LeetCode --- 443周赛
  • 08、Docker学习,常用安装:ClickHouse
  • leetcode122-买卖股票的最佳时机II
  • 设计师必须知道的十个网站/黄页88网推广服务
  • 大良营销网站建设流程/百度平台商家
  • 同程网 网站模板/互联网营销师考试题库
  • 网站要精细是什么意思/seo和sem
  • 个人域名备案快的网站/免费推广有哪些
  • 梅州专业网站建设教程/百度推广优化师是什么