[LeetCode] Remove Boxes 移除盒子


 

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.

Example 1:
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)

 

Note: The number of boxes n would not exceed 100.

 

剛開始看這道題的時候,感覺跟之前那道Zuma Game很像,於是就寫了一個暴力破解的方法,結果TLE了。無奈之下只好上網搜大神們的解法,又看了fun4LeetCode大神寫的帖子,之前那道Reverse Pairs就是參考的fun4LeetCode大神的帖子,驚為天人,這次又是這般精彩,大神請收下我的膝蓋。那么下面的解法就大部分參考fun4LeetCode大神的帖子來講解吧。在之前帖子Reverse Pairs的講解中,大神歸納了兩種重現模式,我們這里也試着看能不能套用上。對於這種看來看去都沒思路的題來說,抽象建模的能力就非常的重要了。對於題目中的具體場景啊,具體代表的東西我們都可忽略不看,這樣能幫助我們接近問題的本質,這道題的本質就是一個數組,我們每次消去一個或多個數字,並獲得相應的分數,讓我們求最高能獲得的分數。而之前那道Zuma Game也是給了一個數組,讓我們往某個位置加數字,使得相同的數字至少有3個才能消除,二者是不是很像呢,但是其實解法卻差別很大。那道題之所以暴力破解沒有問題是因為數組的長度和給定的數字個數都有限制,而且都是相對較小的數,那么即便遍歷所有情況也不會有太大的計算量。而這道題就不一樣了,既然不能暴力破解,那么對於這種玩數組和子數組的題,刷題老司機們都會優先考慮用DP來做吧。既然要玩子數組,肯定要限定子數組的范圍,那么至少應該是個二維的dp數組,其中dp[i][j]表示在子數組[i, j]范圍內所能得到的最高的分數,那么最后我們返回dp[0][n-1]就是要求的結果。

那么對於dp[i][j]我們想,如果我們移除boxes[i]這個數字,那么總得分應該是1 + dp[i+1][j],但是通過分析題目中的例子,能夠獲得高積分的trick是,移除某個或某幾個數字后,如果能使得原本不連續的相同數字變的連續是墜好的,因為同時移除的數字越多,那么所得的積分就越高。那么假如在[i, j]中間有個位置m,使得boxes[i]和boxes[m]相等,那么我們就不應該只是移除boxes[i]這個數字,而是還應該考慮直接移除[i+1, m-1]區間上的數,使得boxes[i]和boxes[m]直接相鄰,那么我們獲得的積分就是dp[i+1][m-1],那么我們剩余了什么,boxes[i]和boxes[m, j]區間的數,此時我們無法處理子數組[m, j],因為我們有些信息沒有包括在我們的dp數組中,此類的題目歸納為不自己包含的子問題,其解法依賴於一些子問題以外的信息。這類問題通常沒有定義好的重現關系,所以不太容易遞歸求解。為了解決這類問題,我們需要修改問題的定義,使得其包含一些外部信息,從而變成自包含子問題

那么對於這道題來說,無法處理boxes[m, j]區間是因為其缺少了關鍵信息,我們不知道boxes[m]左邊相同數字的個數k,只有知道了這個信息,那么m的位置才有意義,所以我們的dp數組應該是一個三維數組dp[i][j][k],表示區間[i, j]中能獲得的最大積分,當boxes[i]左邊有k個數字跟其相等,那么我們的目標就是要求dp[0][n-1][0]了,而且我們也能推出dp[i][i][k] = (1+k) * (1+k)這個等式。那么我們來推導重現關系,對於dp[i][j][k],如果我們移除boxes[i],那么我們得到(1+k)*(1+k) + dp[i+1][j][0]。對於上面提到的那種情況,當某個位置m,有boxes[i] == boxes[m]時,我們也應該考慮先移除[i+1,m-1]這部分,我們得到積分dp[i+1][m-1][0],然后再處理剩下的部分,得到積分dp[m][j][k+1],這里k加1點原因是,移除了中間的部分后,原本和boxes[m]不相鄰的boxes[i]現在相鄰了,又因為二者值相同,所以k應該加1,因為k的定義就是左邊相等的數字的個數。講到這里,那么DP方法最難的遞推公式也就得到了,那么代碼就不難寫了,需要注意的是,這里的C++的寫法不能用vector來表示三維數組,好像是內存限制超出,只能用C語言的寫法,由於C語言數組的定義需要初始化大小,而題目中說了數組長度不會超100,所以我們就用100來初始化,參見代碼如下:

 

解法一:

class Solution {
public:
    int removeBoxes(vector<int>& boxes) {
        int n = boxes.size();
        int dp[100][100][100] = {0};
        return helper(boxes, 0, n - 1, 0, dp);
    }
    int helper(vector<int>& 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);
        for (int m = i + 1; m <= j; ++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;
    }
};

 

下面這種寫法是上面解法的迭代方式,但是卻有一些不同,這里我們需要對dp數組的部分值做一些初始化,將每個數字的所有k值的情況的積分都先算出來,然后在整體更新三維dp數組的時候也很有意思,並不是按照原有的順序更新,而是塊更新,先更新dp[1][0][k], dp[2][1][k], dp[3][2][k]....,再更新dp[2][0][k], dp[3][1][k], dp[4][2][k]...., 再更新dp[3][0][k], dp[4][1][k], dp[5][2][k]....,之前好像也有一道是這樣區域更新的題,但是博主想不起來是哪一道了,以后想起來了再來補充吧,參見代碼如下:

 

解法二:

class Solution {
public:
    int removeBoxes(vector<int>& 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];
    }
};

 

類似題目:

Burst Balloons

Zuma Game

Strange Printer

 

參考資料:

https://leetcode.com/problems/remove-boxes/

https://discuss.leetcode.com/topic/84282/memoization-dfs-c

https://discuss.leetcode.com/topic/84687/java-top-down-and-bottom-up-dp-solutions

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM