[Leetcode][Hard] Remove Boxes 移除盒子 | Java

2,474 阅读1分钟

问题

Remove Boxes Hard

You are 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 (i.e., composed of k boxes, k >= 1), remove them and get k * k points.

Return the maximum points you can get.

  Example 1:

Input: boxes = [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)

Example 2:

Input: boxes = [1,1,1]
Output: 9

Example 3:

Input: boxes = [1]
Output: 1

 

Constraints:

  • 1 <= boxes.length <= 100
  • 1 <= boxes[i] <= 100

解题思路

此题要求算出可能的最大得分,貌似是一个寻找最优子结构的问题,遇到这类问题,一般就会想到大名鼎鼎的动态规划 (Dynamic programming)。动态规划的思想就是把一个问题分解为几个子问题,利用递归求解子问题,推出最终的解。通常会把子问题的解保存下来,用于后续的推导,以避免重复计算。

下文参考大神fun4LeetCode的解决方案,结合笔者的理解来分析解题思路。

动态规划解法的重中之重就是列出状态转移方程,比如 dp(n+1)=dp(n)+1dp(n+1) = dp(n) + 1 。本题是通过消除左右两连的同色盒子来得分,计算结果取决于至少两个维度的因素,因此我们定义dp(i,j)dp(i, j)来表示boxes[i]到boxes[j]之间(包含端点)的最大得分。那么本题就是求解dp(0,n1)dp(0, n-1),而递归运算的终止条件就应该是:

  1. dp(i,i1)=0dp(i, i-1) = 0, 由于j < i, 所以是无效的子集
  2. dp(i,i)=1dp(i, i) = 1, 只有一个盒子,所以结果是1

下面我们试着推导dp(i,j)dp(i,j)的状态转移方程。当处理第一个盒子boxes[i]时,有两种方式:消除,或者不消除。如果直接消除这一个盒子,可以得1分,然后接着处理剩下的盒子。此时的方程可以写成dp(i,j)=11+dp(i+1,j)dp(i,j) = 1*1 + dp(i+1, j)。如果不立即消除,而是等到后面出现相同颜色的盒子(比如boxes[m])时,再将boxes[i]boxes[m]合并到一起消除,有可能拿到更高的分数。这时集合被分为三段:boxes[i]boxes[i+1, m-1]boxes[m, j]

那么问题来了,dp(m,j)dp(m,j)的值收到前面boxes[i]的影响,而 i 并不在boxes[m, j]这个子集合中。也就是说dp(m,j)dp(m,j)不是一个自包含self-contained)的子问题,所以不能用递归方式求解。那么能不能将子问题变成一个自包含的问题呢?

仔细思考之后,不难发现,影响dp(m,j)dp(m,j)的是前面的boxes[i],换个角度说也就是与boxes[m]相同颜色的盒子在前面出现的次数。我们定义k为与子集第一个盒子颜色相同的盒子在前面出现的次数,并把它作为第3种因素加到状态转移方程中,即用dp(i,j,k)dp(i, j, k)表示boxes[i,j]之间的最大得分,且boxes[i]之前有k个与其颜色相同的盒子。本题的最终解就变成了dp(0,n1,0)dp(0, n-1, 0),而递归运算的终止条件就应该是:

  1. dp(i,i1,k)=0dp(i, i-1, k) = 0, 由于j < i, 所以是无效的子集
  2. dp(i,i,k)=(k+1)(k+1)dp(i, i, k) = (k+1)*(k+1), i前面有k个同色的盒子,加上自己就是k+1

我们再来推导一下状态转移方程

  1. 如果直接消除第一个盒子,那么dp(i,j,k)=(k+1)(k+1)+dp(i+1,j,0)dp(i,j,k) = (k+1)*(k+1) + dp(i+1, j, 0)
  2. 如果不立即消除,而是等待m出现,那么dp(i,j,k)=dp(i+1,m1,0)+dp(m,j,k+1),i<m<j,boxes[i]==boxes[m]dp(i,j,k) = dp(i+1, m-1, 0) + dp(m, j, k+1), i < m < j, boxes[i]==boxes[m]。由于[i, j]之间可能出现多个m,我们需要遍历i -> j,计算所有的m,并记录最大得分。

参考答案


public class Solution {
    public int removeBoxes(int[] boxes) {
        int n = boxes.length;
        int[][][] dp = new int[n][n][n];
        return removeSubBoxes(boxes, 0, n-1, 0, dp);
    }

    int removeSubBoxes(int[] boxes, int i, int j, int k, int[][][] dp) {
        // if not valid sub boxes return 0
        if (i > j) {
            return 0;
        }
        // if the dp matrix has been cached, return it
        if (dp[i][j][k] > 0) {
            return dp[i][j][k];
        }
        // record the initial values of i and k
        int i0 = i;
        int k0 = k;
        // count the boxes that has the same color with the first box
        while (i+1 <= j && boxes[i+1] == boxes[i]) {
            i++;
            k++;
        }
        // if we remove i immediately, we get below points
        int res = (k + 1) * (k + 1) + removeSubBoxes(boxes, i + 1, j, 0, dp);
        // or if we wait for m, loop from i to j
        for (int m = i + 1; m <= j; m++) {
            if (boxes[i] == boxes[m]) {
                res = Math.max(res, removeSubBoxes(boxes, i+1, m-1, 0, dp) + removeSubBoxes(boxes, m, j, k+1, dp));
            }
        }
        // when saving to the dp matrix, use the initial values
        dp[i0][j][k0] = res;
        return res;
    }
}

image.png

拓展练习

类似的打气球游戏了解一下?

Burst Balloons

You are given n balloons, indexed from 0 to n - 1. Each balloon is painted with a number on it represented by an array nums. You are asked to burst all the balloons.

If you burst the ith balloon, you will get nums[i - 1] * nums[i] * nums[i + 1] coins. If i - 1 or i + 1 goes out of bounds of the array, then treat it as if there is a balloon with a 1 painted on it.

Return the maximum coins you can collect by bursting the balloons wisely.

 

Example 1:

Input: nums = [3,1,5,8]
Output: 167
Explanation:
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins =  3*1*5    +   3*5*8   +  1*3*8  + 1*8*1 = 167

Example 2:

Input: nums = [1,5]
Output: 10

Constraints:

  • n == nums.length
  • 1 <= n <= 500
  • 0 <= nums[i] <= 100

或者到作者的LeetCode专栏中看看,有没有其他感兴趣的问题吧!

juejin.cn/column/6997…

参考资料