LeetCode 132. 分割回文串 II(经典必会)

LeetCode 132. 分割回文串 II

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

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

示例 1:

输入:s = "aab"

输出:1

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

示例 2:

输入:s = "a"

输出:0

示例 3:

输入:s = "ab"

输出:1

提示:

1 <= s.length <= 2000

s 仅由小写英文字母组成

动态规划

python 复制代码
class Solution:
    def minCut(self, s: str) -> int:
        # dp[i] 表示从第i个字符构成的字符串可以划分成的最少子回文串,字符从0开始计数
        # dp[i] = min(序列S) S中元素为 dp[k] + 1  k from 0 to i-1 且 s[k+1:i]为回文串
        # 每种情况能否成立需要知道+1是否成立,也就是需要知道,右边剩余字符串是否是回文子串
        # 那么如何求的s中任意连续字串是否为回文串呢?

        n = len(s)
        judge = [[False] * n for _ in range(n)]
        for i in range(n - 1):
            g, h = i, i + 1
            while -1 < g < h < n and s[g] == s[h]:
                judge[g][h] = True
                g -= 1
                h += 1
        for i in range(n):
            judge[i][i] = True
            g, h = i - 1, i + 1
            while -1 < g < h < n and s[g] == s[h]:
                judge[g][h] = True
                g -= 1
                h += 1

        dp = [i for i in range(1, n + 1)]
        for i in range(n):
            if judge[0][i]:
                dp[i] = 1

        for i in range(1, n):
            for j in range(i):
                if judge[j + 1][i]:
                    dp[i] = min(dp[i], dp[j] + 1)
        return dp[n - 1] - 1

时间复杂度: O ( n 2 ) O(n^2) O(n2)。计算字符串中所有回文串耗时 O ( n ) O(n) O(n)。动态规划计算最少划分次数外层循环次数函数为 l a y e r 1 ( n ) = n layer1(n)=n layer1(n)=n,求面积得耗时 O ( n ∗ n / 2 ) = O ( n 2 ) O(n*n/2)=O(n^2) O(n∗n/2)=O(n2)。

空间复杂度:字符串回文串判定消耗 O ( n 2 ) O(n^2) O(n2),动态规划dp数组消耗 O ( n ) O(n) O(n),空间复杂度 O ( n 2 ) O(n^2) O(n2)。

本题动态规划的思路相对好想,但是关于字符串中所有回文串的判定处理不好会导致时间复杂度过高,导致超时,预处理序列是一种常用的以空间换时间的方法。下面总结一下本题以及一些拓展:

在 O ( n 2 ) O(n^2) O(n2)耗时求出字符串中所有连续字符子串是否为回文串。

设dp[i][j]表示从第i个字符到第j个字符构成的字串是否为回文串,字符从0开始计数

最经典的分成两种情况讨论。

若回文串为偶数长度,则最中间两个数往外扩展一定对称,所以长度为n的字符串中连续两个数会有n-1对,对每一对数据进行判断即可

若回文串为奇数长度,则选一个中间数字往两边扩展即可,共有n中选法。

python 复制代码
n = len(s)
judge = [[False] * n for _ in range(n)]
for i in range(n - 1):
    g, h = i, i + 1
    while -1 < g < h < n and s[g] == s[h]:
        judge[g][h] = True
        g -= 1
        h += 1
        
for i in range(n):
    judge[i][i] = True
    g, h = i - 1, i + 1
    while -1 < g < h < n and s[g] == s[h]:
        judge[g][h] = True
        g -= 1
        h += 1

而事实上,这里有一种很巧妙地方式可以合并上面两种情况。

奇数和偶数的区别在于,有没有中间值,如果只有一个字符,那么一定是回文串,假设不考虑中间到底有没有字符,那么一个回文串最外面的左右字符一定得相同。假设左边是第i个字符,右边是第j个字符,那么dp[i] == dp[j],若i到j构成回文串那么i+1到j-1也一定构成,则有

dp[i][j] = dp[i] == dp[j] and dp[i+1][j-1]

不过这里有一些特殊情况:

  1. 当i==j时,dp[i+1][j-1]中i+1>j-1导致用到了一个无意义的状态
  2. 当i+1==j时,同理,也用到了一个无意义状态
    不过巧合的是,当用到无意义状态的时候,只需要看dp[i] == dp[j] 即可,所以这里等效可以把无意义状态全部置为True。

则会有完整状态转移方程。

python 复制代码
# 由于在计算dp[i][j]的时候不会用到dp[i][j]本身的值,所以可以同过全部赋值True的方式简化赋初值
# 只需要计算矩阵的一半即可,对角线也无需计算
n = len(s)
judge = [[True] * n for _ in range(n)]
for i in range(n - 1):
    for j in range(i + , n):
        judge[i][j] = judge[i + 1][j - 1] and s[i] == s[j]

另外一个场景

给定数字序列,从第i个数字到第j个数字之和,求出所有情况的和。

蛮力就是三层循环,第一层i,第二层j,第三层枚举求和

但实际上可以优化,求和即可,dp[i]为第0到第i个数字的和那么f[i][j] = dp[j] - dp[i-1]

这样只需要O(n)时间就可以构造好f[i][j]的结果,O(n)通常不会影响到核心解题逻辑的时间复杂度。

还有类似求最大值,给定数组序列,f[i]为除第i个数之外所有数的最大值,最小值也同理。只需遍历一次找到最大值和次大值,第i个数不是最大值f[i]就是最大值,第i个数是最大值f[i]就是次大值。

又如前后缀数组等等。

相关推荐
nurupo1231 分钟前
C++学习路线(二十五)
c++·学习·算法
LNTON羚通1 小时前
算法定制LiteAIServer视频智能分析平台裸土检测技术实现、应用场景与优势概览
大数据·算法·目标检测·音视频·监控
书鸢12362 小时前
力扣每日一题合集
java·算法·leetcode
我不会JAVA!3 小时前
排序算法(3) C++
c++·算法·排序算法
Willliam_william3 小时前
SystemC学习(3)— APB_SRAM的建模与测试
学习·算法
Gui林3 小时前
【GL07】C语言要点
c语言·算法
爱编程— 的小李4 小时前
有序序列合并(c语言)
c语言·算法
云卓SKYDROID4 小时前
无人机反步滑膜控制算法!
算法·无人机·知识科普·云卓科技·反步滑膜控制算法
云卓科技4 小时前
无人机之自动控制原理篇
科技·算法·目标检测·机器人·无人机
云卓科技4 小时前
无人机之集群控制方法篇
科技·算法·无人机·交互·制造