lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

1. 题目

读题

 链接:https://www.nowcoder.com/questionTerminal/a5390d76441647fbb182f34bee6a1ca7
来源:牛客网
一维消消乐

小v在vivo手机的应用商店中下载了一款名为“一维消消乐”的游戏,介绍如下:
1、给出一些不同颜色的豆子,豆子的颜色用数字(0-9)表示,即不同的数字表示不同的颜色;
2、通过不断地按行消除相同颜色且连续的豆子来积分,直到所有的豆子都消掉为止;
3、假如每一轮可以消除相同颜色的连续 k 个豆子(k >= 1),这样一轮之后小v将得到 k*k 个积分;
4、由于仅可按行消除,不可跨行或按列消除,因此谓之“一维消消乐”。
请你帮助小v计算出最终能获得的最大积分。
 
这道题  和 leetcode 546. 移除盒子  是同一道原题

 

考查点

 

2. 解法

思路

  • 定义一个三维数组dp[i][j][k],表示区间[i,j]内,左边有k个与boxes[j]相同的盒子时的最大积分。
  • 动态转移方程是,

dp[i][j][k] = max(calculate(boxes, dp, i, j - 1, 0) + (k + 1) * (k + 1), calculate(boxes, dp, i, m, k + 1) + calculate(boxes, dp, m + 1, j - 1, 0)),其中m是区间[i,j-1]内与boxes[j]相同颜色的盒子的下标。

  • 这个方程的意思是,我们可以选择移除最右边的盒子,或者将区间内与最右边盒子相同颜色的盒子移动到右边,然后计算两个子问题的最大积分和。我们需要在这两种选择中取最大值作为当前状态的值。

 

 

 

这道题很难理解

        # dp[i][j][k] 表示在 [i,j]部分能得到的最大得分
        # k表示boxes[i]左边有k个与之相等的数可以与它结合(可能原本有多余k个数,但是能和它合并一起消失的,只有k个)
        # 如...X DFD X DFDF X SDF [X LSKDFJ X LSJDFLK X DLKFJ...]
        # dp[i][j][k]表示只考虑[]部分对分数的贡献,那么第一个X可能跟着前面K个X消失,也可能等着后面的X一起消失
        # 1. 跟前面的k个一起消失的话 得分为 (k+1)**2 + helper(i+1,j,0)
        # 2.1 跟后面的第二个X一起消失的话,[]中的“LSKDFJ” 部分独立拿分,X成为第二个X开头的子序的前导之一 k->1+k
        #     得分为helper(i+1,m-1,0)+helper(m,j,1+k)  m此时是第二个X的序号
        # 2.2 跟后面的第三个X一起消失的话,[]中的“LSKDFJ X LSJDFLK” 部分独立拿分,X成为第三个X开头的子序的前导之一 k->1+k
        #     得分还是helper(i+1,m-1,0)+helper(m,j,1+k)  m此时是第三个X的序号
        # 2.3 至于“LSKDFJ”与“LSJDFLK”部分分别独立拿分,再合并k+3个X拿分的情况,包含于2.1情况中
        #     因为helper(m,j,1+k) 可以递归,下一层的2.1情况就是前K个与第一个X第二个X和第三个X合并
        #     所以只用考虑[]中第一个X与第Y个X直接合并,中间部分作为子区间独立拿分的情况
        # 几种情况取最高得分,并存入dp[i][j][k]

代码逻辑

 

  • 首先,我们需要定义一个三维数组dp[i][j][k],表示区间[i,j]内,左边有k个与boxes[j]相同的盒子时的最大积分。
  • 然后,我们需要找到状态转移方程,即如何从已知的状态得到未知的状态。
  • 一种基本的情况是,我们只移除最右边的盒子,那么我们可以得到dp[i][j][k] = calculate(boxes, dp, i, j - 1, 0) + (k + 1) * (k + 1),其中calculate是一个递归函数,用来计算子问题的答案。
  • 另一种情况是,我们尝试将区间[i,j-1]内与boxes[j]相同颜色的盒子移动到右边,这样可以增加积分。为了找到这样的盒子,我们需要遍历区间[i,j-1],并且判断是否有boxes[m] == boxes[j]。如果有,那么我们可以将区间[i,j-1]分成两个子区间[i,m]和[m+1,j-1],并且计算它们的最大积分和。
  • 我们需要在这两种情况中选择最大的积分作为dp[i][j][k]的值,并且记录下来,避免重复计算。
  • 最后,我们返回dp[0][n-1][0]作为最终答案,其中n是盒子的数量。

具体实现

class Solution {
    public int removeBoxes(int[] boxes) {
        // dp[i][j][k]表示区间[i,j]内,左边有k个与boxes[j]相同的盒子时的最大积分
        int[][][] dp = new int[100][100][100];
        return calculate(boxes, dp, 0, boxes.length - 1, 0);
    }

    public int calculate(int[] boxes, int[][][] dp, int i, int j, int k) {
        if (i > j) return 0; // 空区间
        if (dp[i][j][k] != 0) return dp[i][j][k]; // 已经计算过
        // 优化:合并右边连续相同颜色的盒子
        while (i < j && boxes[j] == boxes[j - 1]) {
            j--;
            k++;
        }
        // 基本情况:只移除最右边的盒子
        dp[i][j][k] = calculate(boxes, dp, i, j - 1, 0) + (k + 1) * (k + 1);
        // 尝试将区间[i,j-1]内与boxes[j]相同颜色的盒子移动到右边
        for (int m = i; m < j; m++) {
            if (boxes[m] == boxes[j]) {
                // 计算两个子区间的最大积分和
                dp[i][j][k] = Math.max(dp[i][j][k], calculate(boxes, dp, i, m, k + 1) + calculate(boxes, dp, m + 1, j - 1, 0));
            }
        }
        return dp[i][j][k];
    }
}

 

3. 总结

 
posted on 2023-07-19 16:56  白露~  阅读(68)  评论(0编辑  收藏  举报