好的,根据你提供的冗长清单,我仔细检查并过滤后,找到一个在区间动态规划领域非常经典,且未在列表中出现过的题目。它的核心是“选择与决策”,状态定义巧妙,能很好地训练对区间DP的理解。
区间动态规划例题:移除回文子数组的最小操作次数问题
题目描述
给你一个整数数组 arr。每一次操作中,你可以从数组的开头或末尾移除一个回文子数组(即正读反读都一样的连续子数组)。移除后,剩余的部分会重新拼接成一个新的数组。
你的目标是找出移除整个数组 arr 所需的最小操作次数。
例如:
- 输入:
arr = [1, 3, 4, 1, 5]- 第一次操作:移除开头的回文子数组
[1],剩余[3, 4, 1, 5] - 第二次操作:移除末尾的回文子数组
[5],剩余[3, 4, 1] - 第三次操作:移除整个剩余数组
[3, 4, 1],它不是回文,但可以移除其内部的回文子数组吗?让我们思考。 - 实际上,一个更优的方案是:第一次操作直接移除整个数组
[1, 3, 4, 1](这是一个回文子数组,因为首尾都是1,中间3,4对称吗?不,[1, 3, 4, 1]不是回文。1,3,4,1反过来是1,4,3,1,不相同)。所以需要动态规划来求解。
- 第一次操作:移除开头的回文子数组
核心要点:一次操作可以移除任意位置的一个回文子数组,不仅仅是开头或末尾。移除后,数组会“闭合”起来。
解题思路详解
这个问题初看可能不知如何下手,因为一次移除可以发生在数组中间。但我们可以用区间动态规划来刻画整个过程。
步骤1:定义状态
我们定义 dp[i][j] 表示:将子数组 arr[i...j](包含两端 i 和 j)全部移除所需的最小操作次数。
i和j是数组的下标,满足0 <= i <= j < n,其中n是数组长度。- 我们的最终目标是求解
dp[0][n-1]。
步骤2:分析状态转移(思考如何从小区间推导大区间)
对于一个区间 [i, j],我们如何计算移除它的最小次数 dp[i][j] 呢?有几种情况需要考虑:
-
基础情况(最小区间):
- 如果
i == j,即区间只有一个元素,那么它本身就是一个回文数组。一次操作即可移除。所以dp[i][i] = 1。
- 如果
-
如果
arr[i] == arr[j]:- 这是一个非常重要的观察!如果区间两端的元素相等,我们可以“利用”这个相等关系。
- 情况A:如果区间
[i+1, j-1](即去掉头尾的内部区间)本身可以用dp[i+1][j-1]次操作移除,那么对于整个[i, j]区间,我们其实不需要为头尾单独操作。因为当我们进行到某一步时,arr[i]和arr[j]可以和我们移除[i+1, j-1]的某一步操作合并。 - 具体怎么合并?想象一下:
- 假设我们已经用最优方法移除了
[i+1, j-1]。在这个过程中,最后一次操作移除的某个回文子数组,其左边界可能是i+1,右边界可能是j-1。 - 由于
arr[i] == arr[j],我们可以将这个回文子数组向外扩展,把arr[i]和arr[j]包含进来。只要扩展后的子数组仍然是回文的,那么移除[i, j]的总操作次数就和移除[i+1, j-1]一样。 - 但是,
[i+1, j-1]可能为空(即j == i+1),此时dp[i+1][j-1]没有定义。我们规定当i+1 > j-1时,认为内部区间已经为空,移除空区间需要0次操作。所以当j == i+1且arr[i] == arr[j]时,整个区间[i, j]就是一个回文(两个相同元素),dp[i][j] = 1。
- 假设我们已经用最优方法移除了
- 因此,当
arr[i] == arr[j]时:- 如果区间长度
j - i + 1 == 2(即只有两个元素且相等),那么dp[i][j] = 1。 - 如果区间长度更大,那么
dp[i][j]至少可以取dp[i+1][j-1]。注意,dp[i+1][j-1]可能为0吗?只有当i+1 > j-1时,它才代表空区间,我们将其视为0。所以公式为:
dp[i][j] = max(1, dp[i+1][j-1])或者更精确地,如果i+1 <= j-1则dp[i][j] = dp[i+1][j-1];否则dp[i][j] = 1。 - 但是,这只是情况A,我们还需要考虑情况B和通用情况,取最小值。
- 如果区间长度
-
通用情况(分割区间):
- 无论
arr[i]是否等于arr[j],我们都可以将大区间[i, j]分割成两个更小的区间来分别移除。 - 假设我们在位置
k处进行分割(i <= k < j),那么移除[i, j]的方案可以是:先以最优方式移除[i, k],再以最优方式移除[k+1, j]。总操作次数为两者之和:dp[i][k] + dp[k+1][j]。 - 我们需要遍历所有可能的分割点
k,取和的最小值。这是区间DP的经典分割思想。
- 无论
步骤3:总结状态转移方程
综合以上分析,对于 dp[i][j] 的计算,我们取以下几种可能的最小值:
- 基础值:先设一个很大的值(比如
dp[i][j] = j - i + 1,因为最差情况可以一次移除一个元素)。 - 情况A(利用首尾相等):如果
arr[i] == arr[j],- 如果
j - i <= 1(区间长度为1或2),则dp[i][j] = 1。 - 否则(长度>2),
dp[i][j] = min(dp[i][j], dp[i+1][j-1])。这里dp[i+1][j-1]可能为0吗?不会,因为i+1 <= j-1,它至少为1。
- 如果
- 通用分割:对于所有
k满足i <= k < j,dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j])。
关键点:为什么 dp[i][j] 可以等于 dp[i+1][j-1]?这代表了移除 [i, j] 和移除其内部 [i+1, j-1] 的操作次数可以相同。这对应了实际操作中,arr[i] 和 arr[j] 被“附着”在了移除内部区间时的某一次操作上,合并移除了。
步骤4:确定计算顺序
典型的区间DP计算顺序:区间长度从小到大。
- 先计算所有长度为1的区间 (
dp[i][i] = 1)。 - 再计算长度为2的区间(依赖长度为1的区间)。
- 接着计算长度为3的区间(依赖长度为1和2的区间),依此类推,直到计算出长度为
n的区间dp[0][n-1]。
步骤5:举例演算
以 arr = [1, 3, 4, 1, 5] 为例,n = 5。
-
初始化
dp[i][i] = 1。dp[0][0]=1, dp[1][1]=1, dp[2][2]=1, dp[3][3]=1, dp[4][4]=1 -
计算长度为2的区间:
[0,1]: arr[0]=1, arr[1]=3,不相等。只能分割:dp[0][0]+dp[1][1]=2。所以dp[0][1]=2。[1,2]: 3!=4,dp[1][2]=2。[2,3]: 4!=1,dp[2][3]=2。[3,4]: 1!=5,dp[3][4]=2。
-
计算长度为3的区间:
[0,2]: arr[0]=1, arr[2]=4,不相等。分割点k=0,1。- k=0:
dp[0][0]+dp[1][2]=1+2=3 - k=1:
dp[0][1]+dp[2][2]=2+1=3 - min=3。
dp[0][2]=3。
- k=0:
[1,3]: arr[1]=3, arr[3]=1,不相等。分割。- k=1: 1+2=3
- k=2: 2+1=3
dp[1][3]=3。
[2,4]: arr[2]=4, arr[4]=5,不相等。分割。- k=2: 1+2=3
- k=3: 2+1=3
dp[2][4]=3。
-
计算长度为4的区间:
[0,3]: 关键! arr[0]=1, arr[3]=1,相等。且长度4>2。- 首先考虑利用相等:
dp[0][3] = dp[1][2]。dp[1][2]=2。 - 然后考虑分割:
- k=0:
dp[0][0]+dp[1][3]=1+3=4 - k=1:
dp[0][1]+dp[2][3]=2+2=4 - k=2:
dp[0][2]+dp[3][3]=3+1=4
- k=0:
- 取最小值
min(2,4,4,4)=2。所以dp[0][3]=2。这意味着移除[1,3,4,1]只需要2步。
- 首先考虑利用相等:
[1,4]: arr[1]=3, arr[4]=5,不相等。分割。- k=1: 1+3=4
- k=2: 2+2=4
- k=3: 3+1=4
dp[1][4]=4。
-
计算长度为5的区间(最终答案):
[0,4]: arr[0]=1, arr[4]=5,不相等。- 只能分割:
- k=0:
dp[0][0]+dp[1][4]=1+4=5 - k=1:
dp[0][1]+dp[2][4]=2+3=5 - k=2:
dp[0][2]+dp[3][4]=3+2=5 - k=3:
dp[0][3]+dp[4][4]=2+1=3
- k=0:
- 取最小值
min(5,5,5,3)=3。所以dp[0][4]=3。
因此,移除整个数组 [1, 3, 4, 1, 5] 的最小操作次数是 3。
一种可行的3步操作序列(对应 dp[0][4] 的计算路径 dp[0][3] + dp[4][4]):
- 先处理
[0,3]区间[1,3,4,1]。根据dp[0][3]=2,可以这样操作:- 第一步:移除
[4](回文),剩下[1,3,1]。 - 第二步:移除
[1,3,1](首尾都是1,中间是任意数,这个子数组是回文吗?[1,3,1]反过来是[1,3,1],是回文!)。所以两步移除了[0,3]。
- 第一步:移除
- 最后第三步:移除剩下的
[5]。
步骤6:算法复杂度
- 状态数:O(n²)
- 每个状态需要遍历分割点
k,最坏 O(n) 次。 - 总时间复杂度:O(n³)。
- 空间复杂度:O(n²),用于存储
dp表。
总结
这道“移除回文子数组”问题,其精髓在于状态 dp[i][j] 的定义,以及当 arr[i] == arr[j] 时,dp[i][j] 可以直接继承 dp[i+1][j-1] 这一巧妙的状态转移。它结合了回文判断和区间分割的思想,是区间动态规划中一道非常锻炼思维能力的题目。通过从小到大计算区间,我们最终得到了移除整个数组的最优解。