题目描述
给你一个整数数组 arr
,每一次操作你都可以选择并删除它的一个 回文 子数组 arr[i], arr[i+1], ..., arr[j]
,其中 i <= j
。
注意,每当你删除掉一个子数组,右侧元素都会自行向前移动填补空位。
请你计算并返回从数组中删除所有数字所需的最少操作次数。
样例
输入:arr = [1,2]
输出:2
输入:arr = [1,3,4,1,5]
输出:3
解释:先删除 [4],然后删除 [1,3,1],最后再删除 [5]。
限制
1 <= arr.length <= 100
1 <= arr[i] <= 20
算法
(动态规划,区间模型) O(n3)
- 设 f(i,j) 表示删除区间 [i,j] 所需要的最少操作次数。
- 初始时,f(i,i)=1,f(i,i−1)=0。其余为正无穷。
- 转移时,可以将 arr[i] 单独考虑删除,则转移 f(i,j)=min(f(i,j),1+f(i+1,j))。如果 arr[i]==arr[i+1],则这两个可以一起删除,转移 f(i,j)=min(f(i,j),1+f(i+2,j))。如果 arr[i]==arr[k],(i+2<=k<=j),则 arr[i] 和 arr[k] 可以随着区间 [i+1,k−1] 一起删除,转移 f(i,j)=min(f(i,j),f(i+1,k−1)+f(k+1,j))。
- 最终答案为 f(0,n−1)。
时间复杂度
- 状态数为 O(n2) 个,转移需要 O(n) 的时间,故时间复杂度为 O(n3)。
空间复杂度
- 需要额外 O(n2) 的空间存储状态。
C++ 代码
class Solution {
public:
int minimumMoves(vector<int>& arr) {
int n = arr.size();
vector<vector<int>> f(n + 1, vector<int>(n + 1));
for (int i = 0; i <= n; i++)
f[i][i] = 1;
for (int i = 1; i <= n; i++)
f[i][i - 1] = 0;
for (int len = 2; len <= n; len++)
for (int i = 0; i < n - len + 1; i++) {
int j = i + len - 1;
f[i][j] = 1 + f[i + 1][j];
if (arr[i] == arr[i + 1])
f[i][j] = min(f[i][j], 1 + f[i + 2][j]);
for (int k = i + 2; k <= j; k++)
if (arr[i] == arr[k])
f[i][j] = min(f[i][j], f[i + 1][k - 1] + f[k + 1][j]);
}
return f[0][n - 1];
}
};
为什么f申请了n + 1长度,最后却使用了f[0][n - 1]而不是f[1][n]
for (int k = i + 2; k <= j; k++) if (arr[i] == arr[k]) f[i][j] = min(f[i][j], f[i + 1][k - 1] + f[k + 1][j]);
这段代码里可能会用到
f[n][n - 1]
。