Hard
题目描述
给出一些不同颜色的盒子,颜色由不同的正数表示。
你将经过若干轮操作去去掉盒子,直到所有的盒子都去掉为止。每一轮你可以移除具有相同颜色的连续盒子序列(由 k 个盒子组成,k >= 1),这样的话就会获得 k * k 个积分。
返回你能获得的最大积分和。
示例 1:
输入:boxes = [1,3,2,2,2,3,4,3,1]
输出:23
解释:
[1, 3, 2, 2, 2, 3, 4, 3, 1]
----> [1, 3, 3, 4, 3, 1] (3*3=9 points)
----> [1, 3, 3, 3, 1] (1*1=1 points)
----> [1, 1] (3*3=9 points)
----> [] (2*2=4 points)
示例 2:
输入:boxes = [1,1,1]
输出:9
示例 3:
输入:boxes = [1]
输出:1
提示:
- 1 <= boxes.length <= 100
- 1 <= boxes[i] <= 100
解题思路
这是一道经典的区间动态规划题目,核心思想是考虑如何合并相同颜色的盒子来获得最大得分。
基本思路:
使用记忆化递归的动态规划方法。定义 dp(i, j, k) 表示在区间 [i, j] 内移除所有盒子,且在位置 j 右边还有 k 个与 boxes[j] 相同颜色的盒子时能获得的最大得分。
状态转移:
- 如果
i > j,返回 0 - 先将 j 向左扩展,找到所有与
boxes[j]相同颜色的连续盒子,统计数量 - 对于每个可能的分割点 m(其中
boxes[m] == boxes[j]),尝试两种策略:- 直接移除右边的相同颜色盒子:
dp(i, j-1, 0) + (k+1)*(k+1) - 先移除中间部分,再合并相同颜色:
dp(i, m, k+1) + dp(m+1, j-1, 0)
- 直接移除右边的相同颜色盒子:
优化要点:
- 使用记忆化避免重复计算
- 预处理相同颜色的连续盒子以减少状态数量
- 三维DP的状态设计是关键,第三维k表示右边等待合并的相同颜色盒子数量
时间复杂度为 O(n³),空间复杂度为 O(n³),其中 n 是盒子数量。
代码实现
class Solution {
public:
int removeBoxes(vector<int>& boxes) {
int n = boxes.size();
vector<vector<vector<int>>> memo(n, vector<vector<int>>(n, vector<int>(n, -1)));
return dp(boxes, 0, n - 1, 0, memo);
}
private:
int dp(vector<int>& boxes, int i, int j, int k, vector<vector<vector<int>>>& memo) {
if (i > j) return 0;
if (memo[i][j][k] != -1) return memo[i][j][k];
// 将j向左扩展,找到所有相同颜色的连续盒子
while (i <= j - 1 && boxes[j] == boxes[j - 1]) {
j--;
k++;
}
// 直接移除boxes[j]及其右边的k个相同颜色盒子
int result = dp(boxes, i, j - 1, 0, memo) + (k + 1) * (k + 1);
// 尝试与左边的相同颜色盒子合并
for (int m = i; m < j; m++) {
if (boxes[m] == boxes[j]) {
result = max(result,
dp(boxes, i, m, k + 1, memo) + dp(boxes, m + 1, j - 1, 0, memo));
}
}
return memo[i][j][k] = result;
}
};
class Solution:
def removeBoxes(self, boxes: List[int]) -> int:
n = len(boxes)
memo = {}
def dp(i, j, k):
if i > j:
return 0
if (i, j, k) in memo:
return memo[(i, j, k)]
# 将j向左扩展,找到所有相同颜色的连续盒子
orig_j = j
while j > i and boxes[j] == boxes[j - 1]:
j -= 1
k += 1
# 直接移除boxes[j]及其右边的k个相同颜色盒子
result = dp(i, j - 1, 0) + (k + 1) * (k + 1)
# 尝试与左边的相同颜色盒子合并
for m in range(i, j):
if boxes[m] == boxes[j]:
result = max(result,
dp(i, m, k + 1) + dp(m + 1, j - 1, 0))
memo[(i, orig_j, k)] = result
return result
return dp(0, n - 1, 0)
public class Solution {
private int[,,] memo;
public int RemoveBoxes(int[] boxes) {
int n = boxes.Length;
memo = new int[n, n, n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
memo[i, j, k] = -1;
}
}
}
return Dp(boxes, 0, n - 1, 0);
}
private int Dp(int[] boxes, int i, int j, int k) {
if (i > j) return 0;
if (memo[i, j, k] != -1) return memo[i, j, k];
// 将j向左扩展,找到所有相同颜色的连续盒子
while (i <= j - 1 && boxes[j] == boxes[j - 1]) {
j--;
k++;
}
// 直接移除boxes[j]及其右边的k个相同颜色盒子
int result = Dp(boxes, i, j - 1, 0) + (k + 1) * (k + 1);
// 尝试与左边的相同颜色盒子合并
for (int m = i; m < j; m++) {
if (boxes[m] == boxes[j]) {
result = Math.Max(result,
Dp(boxes, i, m, k + 1) + Dp(boxes, m + 1, j - 1, 0));
}
}
return memo[i, j, k] = result;
}
}
var removeBoxes = function(boxes) {
const n = boxes.length;
const memo = new Map();
function dp(i, j, k) {
if (i > j) return 0;
const key = `${i},${j},${k}`;
if (memo.has(key)) return memo.get(key);
// 将j向左扩展,找到所有相同颜色的连续盒子
const origJ = j;
while (j > i && boxes[j]
复杂度分析
| 复杂度 | 大小 |
|---|---|
| 时间复杂度 | O(n³) |
| 空间复杂度 | O(n³) |
其中 n 为盒子数量。时间复杂度来自于三重循环的状态转移,空间复杂度来自于三维记忆化数组。