LeetCode 132:分割回文串 II

LeetCode 132:分割回文串 II

问题本质与核心挑战

给定字符串 s,需将其分割为若干回文子串 ,求最少分割次数。核心挑战:

  • 直接枚举所有分割方式(指数级复杂度)不可行;
  • 需结合 动态规划 优化分割次数计算,并通过 回文预处理 加速判断。

核心思路:动态规划 + 回文预处理

1. 回文预处理(减少重复判断)

用二维数组 isPal[i][j] 记录 子串 s[i..j] 是否为回文 ,预处理后可 O(1) 查询:

  • 单个字符 :isPal[i][i] = true;
  • 两个字符 :isPal[i][i+1] = (s[i] == s[i+1]);
  • 长度 ≥3 :isPal[i][j] = (s[i] == s[j] && isPal[i+1][j-1])(依赖更短的子串结果)。
2. 动态规划定义与转移
  • 状态定义 :dp[i] 表示 前 i 个字符(s[0..i-1]) 的最少分割次数。
  • 初始条件 :
    • dp[0] = -1(空字符串的分割次数为 -1,方便后续计算);
    • dp[i] = i-1(最坏情况:每个字符单独分割,如 "abc" 需要 2 次分割)。
  • 状态转移 :
    对于每个 j(前 j 个字符),遍历所有可能的分割点 i(0 ≤ i < j):
    • 若 s[i..j-1] 是回文(isPal[i][j-1] = true),则 dp[j] = min(dp[j], dp[i] + 1)。

算法步骤详解(以示例 s = "aab" 为例)

步骤 1:预处理回文子串(isPal 数组)
子串范围 [i,j] 长度 判断逻辑 isPal[i][j]
[0,0] 1 单个字符 true
[1,1] 1 单个字符 true
[2,2] 1 单个字符 true
[0,1] 2 s[0]='a' == s[1]='a' true
[1,2] 2 s[1]='a' ≠ s[2]='b' false
[0,2] 3 s[0]≠s[2](直接不满足) false
步骤 2:初始化动态规划数组(dp)
  • dp[0] = -1(空字符串的分割次数);
  • dp[1] = 0(前1个字符 "a",无需分割);
  • dp[2] = 1(初始值,后续会被更新);
  • dp[3] = 2(初始值,后续会被更新)。
步骤 3:状态转移计算

遍历 j(前 j 个字符)和 i(分割点):

j(前j字符) i(分割点) 子串 s[i..j-1] 是否回文(isPal[i][j-1]) dp[i] + 1 dp[j] 更新后的值
j=1 i=0 "a" true -1 + 1 = 0 min(0, 0) = 0
j=2 i=0 "aa" true -1 + 1 = 0 min(1, 0) = 0
j=2 i=1 "a" true 0 + 1 = 1 min(0, 1) = 0
j=3 i=0 "aab" false - 不更新
j=3 i=1 "ab" false - 不更新
j=3 i=2 "b" true 0 + 1 = 1 min(2, 1) = 1

完整代码(Java)

java 复制代码
class Solution {
    public int minCut(String s) {
        int n = s.length();
        if (n == 0) return 0;
        
        // 步骤1:预处理回文子串
        boolean[][] isPal = new boolean[n][n];
        // 处理长度为1的回文
        for (int i = 0; i < n; i++) {
            isPal[i][i] = true;
        }
        // 处理长度为2的回文
        for (int i = 0; i < n - 1; i++) {
            isPal[i][i + 1] = (s.charAt(i) == s.charAt(i + 1));
        }
        // 处理长度≥3的回文
        for (int len = 3; len <= n; len++) {
            for (int i = 0; i + len <= n; i++) {
                int j = i + len - 1;
                isPal[i][j] = (s.charAt(i) == s.charAt(j) && isPal[i + 1][j - 1]);
            }
        }
        
        // 步骤2:动态规划
        int[] dp = new int[n + 1];
        // 初始条件:最坏情况,每个字符单独分割
        for (int i = 0; i <= n; i++) {
            dp[i] = i - 1;
        }
        
        // 状态转移:遍历前j个字符,尝试所有分割点i
        for (int j = 1; j <= n; j++) {
            for (int i = 0; i < j; i++) {
                if (isPal[i][j - 1]) { // s[i..j-1]是回文
                    dp[j] = Math.min(dp[j], dp[i] + 1);
                }
            }
        }
        
        return dp[n];
    }
}

关键逻辑解析

  1. 回文预处理 :通过动态规划预处理所有子串的回文性,避免每次判断回文时重复计算,时间复杂度 O(n²)。
  2. 动态规划状态 :dp[j] 表示前 j 个字符的最少分割次数,利用已计算的 dp[i] 快速推导,时间复杂度 O(n²)。
  3. 初始条件优化 :dp[0] = -1 是为了让 dp[1] 的计算更自然(dp[0] + 1 = 0,对应单个字符无需分割)。

该方法通过 预处理+动态规划 高效解决问题,时间复杂度为 O(n²),可处理题目中 n ≤ 2000 的规模。核心是将"回文判断"和"分割次数计算"解耦,通过预处理降低重复判断的开销,再利用动态规划的状态转移快速推导最优解。

相关推荐
学术小李20 分钟前
cudaMalloc第一个参数为何是指针的指针
算法
kukubuzai23 分钟前
二分查找系列一
数据结构·算法·leetcode
老歌老听老掉牙27 分钟前
动态规划:从递归到最优决策的算法艺术
算法·动态规划
朔北之忘 Clancy30 分钟前
2024 CSP-J 第二轮真题解析
c++·算法·青少年编程·noip·csp·信奥赛·noi
小七在进步1 小时前
类和对象(五)
java·开发语言·算法
就叫年华吧丶1 小时前
长文档点目录定位总是不准?一个 content-visibility + 平滑滚动引发的连环坑(Vue3 实战)
前端·javascript·算法·vue
FPGA信号处理1 小时前
【信号检测与估计】第四节课:非高斯噪声下的 BLUE、极大似然与 EM 算法
人工智能·算法·机器学习
.道阻且长.2 小时前
C++ 11:可变参数模板
前端·c++·算法
Doubbbbbbble云4 小时前
基于贪心算法的区间覆盖问题求解策略4
算法·贪心算法