1246. 删除回文子数组
大约 4 分钟
---
1246. 删除回文子数组
- 标签:数组、动态规划
- 难度:困难
题目链接
题目大意
描述:给定一个整数数组 。每一步操作中,可以删除一个连续的回文子数组(子数组内元素连续,且回文指正反顺序读相同)。删除后,剩余部分会左右拼接起来。
要求:返回将整个数组删除所需的最少操作次数。
说明:
- 。
- 。
示例:
- 示例 1:
输入:arr = [1,2]
输出:2
解释:不能一次性删除,因为 [1,2] 不是回文。需要先删 [1] 再删 [2]。- 示例 2:
输入:arr = [1,3,4,1,5]
输出:3
解释:可以这样操作:删除 [4] → [1,3,1,5] → 删除 [1,3,1] → [5] → 删除 [5]。解题思路
思路 1:区间动态规划
1. 核心思想
删除回文子数组后,剩余部分会拼接起来。这意味着一次删除可能"拉近"原本不相邻的元素,产生新的回文。比如 删除中间的 后, 变成连续的子数组,可以一次删除。
这类似于经典的"移除回文子序列"或"戳气球"类问题,可以用区间 DP 解决。定义 表示删除子数组 (从 到 ,包含两端)所需的最少操作次数。
2. 阶段划分
按区间长度从小到大的顺序递推。先计算长度为 的区间,再计算长度为 的区间,逐渐扩展到整个数组。
3. 定义状态
表示删除 这个子数组所需的最少操作次数。
4. 状态转移方程
基础情况:
- (一个元素可以一次删除)。
- 如果 ,(两个相同元素构成回文,一次删除);否则 。
一般情况(区间长度 ):
有两种策略:
分段删除:将区间分成两段分别删除,操作次数相加:
首尾相同:如果 ,那么删除中间部分后再删首尾,可以省去一次操作:
- 如果 ,(已在基础情况中处理)。
- 否则 (因为 和 可以"搭车"和中间的子数组一起被删除,或者等中间删除后,首尾两个相同元素构成回文一次删除)。
这个转移可能有点让人困惑,用具体的例子理解:
5. 结合示例走一遍
计算 (只展示部分关键步骤):
- : → 。: → 。: → 。: → 。
区间长度 :
- :分段 。 → 不进入情况 2。。
- :分段 。 → 。
- :分段 。 → 。
区间长度 :
- :分段 。 → (先删中间 再一起删首尾 )。
- :分段 。 → 。
区间长度 :
- :分段 。 → 结果为 。
,对应操作:先删 (位置 2)→ → 删 → → 删 。
思路 1:代码
class Solution:
def minimumMoves(self, arr: List[int]) -> int:
n = len(arr)
dp = [[0] * n for _ in range(n)]
# 区间长度为 1
for i in range(n):
dp[i][i] = 1
# 区间长度为 2
for i in range(n - 1):
dp[i][i + 1] = 1 if arr[i] == arr[i + 1] else 2
# 区间长度 >= 3
for length in range(3, n + 1):
for i in range(n - length + 1):
j = i + length - 1
# 策略 1:分段删除
dp[i][j] = float('inf')
for k in range(i, j):
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j])
# 策略 2:首尾相同,可以一次消掉
if arr[i] == arr[j]:
dp[i][j] = min(dp[i][j], dp[i + 1][j - 1])
return dp[0][n - 1]思路 1:复杂度分析
- 时间复杂度:,其中 是数组长度。三层循环:区间长度、区间起点、分割点。, 次操作可接受。
- 空间复杂度:,存储 表。