leetcode 546. 移除盒子 —— 动态规划

将上面记忆化存储的递归算法,改为递推算法。即动态规划法。

546. 移除盒子

题目:
546. 移除盒子
给出一些不同颜色的盒子,盒子的颜色由数字表示,即不同的数字表示不同的颜色。
你将经过若干轮操作去去掉盒子,直到所有的盒子都去掉为止。每一轮你可以移除具有相同颜色的连续 k 个盒子(k >= 1),这样一轮之后你将得到 k*k 个积分。
当你将所有盒子都去掉之后,求你能获得的最大积分和。

示例 1:

输入:
[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 分) 
----> [1, 3, 3, 3, 1] (1*1=1 分) 
----> [1, 1] (3*3=9 分) 
----> [] (2*2=4 分)

提示:盒子的总数 n 不会超过 100。

题目来源:力扣(LeetCode)
https://leetcode-cn.com/problems/remove-boxes

解题思路:

这道题…,完全想不到,不会。这里直接看了 leetcode 官方的代码,总结一下自己对官方解答思路的理解。

这道题就是每次取出相同并且相邻的颜色盒子,使某种指标函数达到最大。如果取出某个位置上连续相同颜色的盒子,并且剩余相邻前后的盒子位置关系不变,即取出盒子后其位置变为空,这样剩余前后相邻位置始终不会连续,也就不可以一起取出,这样随便怎么取,取出后对剩余指标不影响,上述指标结果都一样,这样这个题就毫无意义了。
leetcode 546. 移除盒子 —— 动态规划_第1张图片
这道题目要求盒子拿去后,剩余先后相邻的盒子连接起来,如果这两个盒子颜色相同,则变成连续相同的元素,可以一起取出。
leetcode 546. 移除盒子 —— 动态规划_第2张图片
这样取出盒子后,得到一个新的数组,并且不同的取法得到不同的指标函数值。比如上列数组,取后面四个数为例,若先取出右边两个3,再取出4,再取出左边的3,指标函数值为 2*2 + 1*1 +1*1 = 6。若先取出4,则左边的3和右边两个连续的3相连接,可以一起取出,指标函数值为 1*1 + 3*3 = 10。
leetcode 546. 移除盒子 —— 动态规划_第3张图片
这样取出的顺序、取出的盒子都会影响最终的指标函数值。那么如何决定取出顺序和取出哪种颜色的盒子?(我也不知道)。可以考虑暴力法遍历所有情况。

根据官方解题思路:既然使用动态规划,首先如何将问题化为一系列的子问题、子子问题等,划分阶段、得到状态变量的取值。继续考虑上述数组,从最右边开始思考(左边也可以),首先考虑最右边的3该如何取出(连续相同的绑在一起),并且以这里为第一阶段,根据数组中3的取出情况可以确定第一阶段状态变量的不同状态(多少个子问题)。如下:(子问题又化为子子问题,这样递推下去)
leetcode 546. 移除盒子 —— 动态规划_第4张图片
为什么要从数组两端开始:因为这样划分的子问题可以很方便的包含不同元素先后取出得到不同的结果。即子问题 1 最右边 3 最先取出,包含其他元素与其余 3 取出先后顺序的所有情况。子问题 2、3 其他元素先取出,包含将最右边的 3 与其他的 3 连接一起取出的所有情况。这样分解原问题主要考虑到相邻两个不相同盒子之间取出的先后顺序所描述的不同情况

进一步解释计算过程:设一个三维数组 dp[left][right][k]表示当前数组的指标函数值,left 表示一个数组的左边界索引,right 表示数组的右边界索引,即这两个数代表一段数组,k 表示数组右边界有几个元素与该数组右边界元素相同。设数组长为 n,计原问题的解为dp[0][n-1][0]。如
leetcode 546. 移除盒子 —— 动态规划_第5张图片
原问题的解与子问题的解的关系:
leetcode 546. 移除盒子 —— 动态规划_第6张图片
描述不出来了,接下来看代码理解:

动态规划+递归代码:

public int removeBoxes(int[] boxes) {
    // 用于表示数值的指标函数值。
    int[][][] dp = new int[100][100][100];
    // 原问题的解
    return calculatePoints(boxes,0,boxes.length-1,dp,0);
}
private int calculatePoints(int[] boxes,int left,int right,int[][][]dp,int k){
    if(left > right){
        return 0;
    }
    if(dp[left][right][k] != 0){
        return dp[left][right][k];
    }
    // 判断数组最右边有几个连续相同的元素,不包括最终子数组最右边的元素
    while (right>left && boxes[right-1] == boxes[right]){
        right--;  // 数组右边界减小
        k++;      // 与右边界元素连续相同的元素个数
    }
    // 子问题 1 的结果
    dp[left][right][k] = calculatePoints(boxes,left,right-1,dp,0) + (k+1)*(k+1);
    // 子问题 2,3 ... 的解,并将所有子问题比较取最大的指标函数值。
    for (int i = left; i < right-1; i++) {
        if (boxes[i] == boxes[right]){
            dp[left][right][k] = Math.max(dp[left][right][k],calculatePoints(boxes,left,i,dp,k+1)+calculatePoints(boxes,i+1,right-1,dp,0));
        }
    }
    return dp[left][right][k];
}

你可能感兴趣的:(算法刷题)