题目概述
Given several boxes with different colors represented by different positive numbers.
You may experience several rounds to remove boxes until there is no box left. Each time you can choose some continuous boxes with the same color (composed of k boxes, k >= 1), remove them and get k*k points.
Find the maximum points you can get.
Input:
[1, 3, 2, 2, 2, 3, 4, 3, 1]
Output:
23
Explanation:
[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)
分析
题目的大意是,给定一组不同颜色、任意排列的盒子(颜色通过不同的正整数表示)。现在不断消除连续相同颜色的盒子,每一次消除都可以获得k*k分,其中k为消去盒子的个数。题目的目标是找出一种消除盒子的策略,使得全部消除盒子的同时,获取最高的分数。
显然这道题目需要用到动态规划的思想,但这道题我们一开始却无法通过简单的单向递推得到计算式,因为观察题目,获得高分的诀窍是移除某个或某几个数字之后,能够使得原本不连续的相同数字变成连续的,因为同时移除的数字越多,获得的积分就越高。换言之,我们考虑一段区间[i,j],如果在这段区间的中间存在一个数m,使得boxes[i] = boxes[m],那么我们就不能只移除boxes[i],而是要考虑移除[i+1,m-1]区间的这些数,从而使boxes[i]和boxes[m]直接相邻。然而,在这种情况下,子数组[m,j]的区间信息就没有被包含如dp数组,这将导致单纯使用递归无法处理这个子数组。因此,我们需要在传统的动态规划思想上进行优化。
鉴于上述的讨论,我们要处理boxes[m,j],还需要知道boxes[m]左边相同数字的个数k,如此m的位置才有意义。因此dp数组应该是一个三维数组dp[i][j][k],表示区间[i,j]之间能够获得的最大积分。对于dp[i][j[][k],如果移除boxes[i],则得到的结果是(1+k)*(1+k)+dp[i+1][j][0]。而对于此前讨论到的boxes[i] = boxes[m]的问题,优先考虑移除[i+1,m-1]的部分,得到dp[i+1][m-1][0],在处理剩余部分得到dp[i+1][m-1][k+1],这是因为boxes[m]和原本不相邻的boxes[i]相邻,且二者值相同,因此k需要+1,注意k的定义原本就是左侧相等数字的个数。最后显然,递推结束的标志是boxes[i]左侧有k个数字与其相等,那么所求目标就是dp[0][n-1][0]。
经过了如上分析,我们尝试性实现递推:
int removeBoxes(vector& boxes) {
int n = boxes.size();
int dp[100][100][100] = {0};
return helper(boxes, 0, n-1, 0, dp); // dp[0][n-1][0]
}
int helper(vector& boxes, int i, int j, int k, int dp[100][100][100]) {
if (j < i) return 0;
if (dp[i][j][k] > 0) return dp[i][j][k];
int res = (1+k)*(1+k) + helper(boxes, i+1, j, 0, dp); // dp[i+1][j][0] + (1*k)^2
for (int m = i+1; m <= j; m++) // 子区间[i+1, m]
if (boxes[m] == boxes[i])
res = max(res, helper(boxes, i+1, m-1, 0, dp) + helper(boxes, m, j , k+1, dp));
return dp[i][j][k] = res;
}
如果要写成更容易理解的循环形式:
int removeBoxes(vector& boxes) {
int n = boxes.size();
int dp[n][n][n] = {0};
for (int i = 0; i < n; ++i) {
for (int k = 0; k <= i; ++k) {
dp[i][i][k] = (1 + k) * (1 + k);
}
}
for (int t = 1; t < n; ++t) {
for (int j = t; j < n; ++j) {
int i = j - t;
for (int k = 0; k <= i; ++k) {
int res = (1 + k) * (1 + k) + dp[i + 1][j][0];
for (int m = i + 1; m <= j; ++m) {
if (boxes[m] == boxes[i]) {
res = max(res, dp[i + 1][m - 1][0] + dp[m][j][k + 1]);
}
}
dp[i][j][k] = res;
}
}
}
return n == 0 ? 0 : dp[0][n - 1][0];
}
算法的时间复杂度和空间复杂度均为O(n^3)。
总结
这个题目让我回忆起了小时候常玩的游戏祖玛,祖玛中并不能完全通过“见什么消什么”这种简单策略来进行消除,事实上后期关卡中彩球出现的颜色几乎是随机的,必须要进行一定的构造才可以实现连续消除、最大化分数效益的结果。同理,对于此题也是,在消除盒子时更重要的是考虑如何让原本不连续的盒子变成连续的,因此需要不断地对分离计算子区间的最大分数效益。