LeetCode312.BurstBalloons


  原題鏈接:https://leetcode.com/problems/burst-balloons/ 

  題目的大意是:給你一串氣球,每個氣球上都標了一個數字,這些數字用一個數組nums來表示。如果你扎破第i個氣球,你就可以獲得 nums[left] * nums[i] * nums[right] 個硬幣,其中left和right是與第i個氣球相鄰的兩個氣球,當i被扎破后,left和right就變成直接相鄰了。找出一個扎破所有氣球的順序,使得最后獲得的硬幣數量的總和最大。

  解決這道題的思路是動態規划,這道題和經典的矩陣連乘問題很相似,我的算法分析也借鑒了矩陣連乘的求解思路。首先,我們考慮將問題分解成一個或多個子問題,假設第i個氣球是第一個扎破的氣球,我們可以得到 nums[i - 1] * nums[i] * nums[i + 1] + maxCoins(int[] newNums) 個硬幣,其中newNums表示的是刪掉 nums[i] 后由數組nums中剩余元素構成的新數組。如果以這個順序來分解問題,我們每次都要重新調整數組的結構,並且在這種分解順序下,原問題和子問題難以用簡潔的標識符來表示。因此,我們換一種方式考慮,如果假設第i個氣球是最后一個扎破的氣球, 剩下的氣球不管以什么樣的順序扎破,位於第i個氣球的左邊的所有氣球,它們的“right”值只可能小於等於i,而位於第i個氣球右邊的所有氣球,它們的“left”值只可能大於等於i。因此,如果第i個氣球最后扎破,那么它左右兩邊的氣球的扎破順序可以分開來求(按任何順序扎破左半邊的所有氣球不會影響右半邊的最大值,右半邊同理),並且在分解問題的過程中我們不需要調整數組nums的結構。所以,以 maxCoins[0][n - 1]表示原問題的解,則可以做如下分解:

maxCoins[0][n - 1] = maxCoins[0][i - 1] + maxCoins[i + 1][n - 1] + nums[left] * nums[i] * nums[right]

  接下來需要考慮 left 和 right 的取值以及邊界問題。對於最后一個扎破的氣球i,得到的硬幣數為 nums[-1] * nums[i] * nums[n],其中 nums[-1] = nums[n] = 1。為了方便計算,我們可以將原數組加上首尾兩個元素,即 nums[-1] 和 nums[n],用一個長度為 n + 2 新的數組,tnums[],來表示。為了方便計算,我們用 maxCoins[low][high] 表示在數組 tnums 中下標范圍為 (low,high),或者說是 [low + 1,high - 1] 的氣球所能獲得的最大硬幣個數,注意這是個開區間,所以原問題的解即 maxCoins[0][tnums.length - 1]。接下來要確定 left 和 right 的取值,對於 maxCoins[low][high],當數組中只剩下最后一個氣球k時, 根據題目中給出的例子,扎破這個氣球得到的硬幣個數是: tnums[low] * tnums[k] * tnums[high] 。寫出狀態轉移方程如下:

maxCoins[low][high] = max{maxCoins[low][k] + maxCoins[k][high] + tnums[low] * tnums[k] * tnums[high] , low < k <high && (high - low) > 2}

                                    or    = tnums[low] * tnums[k] * tnums[high] , (high - low) == 2

  根據狀態轉移方程,我們可以推算出計算子問題的順序,由 low < k  &&  high > k 可得,maxCoins矩陣的計算順序為大的行下標,小的列下標開始計算,又要保證 low < high,所以計算順序為一層一層的反對角線,這樣可以保證每個問題在求算時需要的子問題已經求算完畢。求算順序由下圖所示:

  下面是我用Java寫的代碼:

     

 1 import java.util.Stack;
 2 
 3 public class LeetCode312 {
 4     public int maxCoins(int[] nums) {
 5         int[] tnums = new int[nums.length + 2];
 6         int n = 1;
 7         for (int num : nums) {
 8             tnums[n] = num;
 9             n++;
10         }
11         tnums[0] = tnums[n++] = 1;
12 
13         int[][] coins = new int[n][n];
14         int[][] burstorders = new int[n][n];
15 
16         int low, high, k;
17         for (low = n - 1; low >= 0; low--) {
18             for (high = low + 2; high < n; high++) {
19                 if ((high - low) == 2) {
20                     coins[low][high] = tnums[low] * tnums[(low + high) / 2] * tnums[high];
21                     burstorders[low][high] = (low + high) / 2;
22                     System.out.println("calculating value of coin[" + low + "][" + high + "]=" + coins[low][high]);
23                 } else {
24                     for (k = low + 1; k <= high - 1; k++) {
25                         int tcoins = coins[low][k] + coins[k][high] + tnums[low] * tnums[k] * tnums[high];
26                         //coins[low][high] = coins[low][high] > tcoins ? coins[low][high] : tcoins;
27                         if(coins[low][high] < tcoins){
28                             coins[low][high] = tcoins;
29                             burstorders[low][high] = k;//記錄氣球爆破順序
30                         }
31                     }
32                 }
33             }
34         }
35         
36         System.out.println("The bursting order is:");
37         printOrders(burstorders,0,n - 1);
38 
39         return coins[0][n - 1];
40     }
41     
42     public void printOrders(int[][] orders,int low,int high){
43         /*recursive 
44          * the output is reverse order
45         if((high - low) == 2){
46             System.out.println("[" + low + "][" + high + "]:" + (orders[low][high] - 1) + " ");
47         }else if((high - low) > 2){
48             int last = orders[low][high];
49             System.out.println("[" + low + "][" + high + "]:" + (last - 1) + " ");
50             printOrders(orders,low,last);
51             printOrders(orders,last,high);
52         }*/
53         
54         //non-recursive
55         class Pair{
56             private int x;
57             private int y;
58             
59             public Pair(int x,int y){
60                 this.x = x;
61                 this.y = y;
62             }
63         }
64         //因為orders矩陣記錄的是最后一個扎破的氣球,所以正好用棧把順序倒過來
65         Stack<Integer> burstballoons = new Stack<>();
66         Stack<Pair> stk = new Stack<>();
67         Pair p = null;
68         stk.push(new Pair(low,high));
69         
70         while(!stk.isEmpty()){
71             p = stk.pop();
72             if((p.y - p.x) == 2){
73                 burstballoons.push(orders[p.x][p.y] - 1);
74             }else if((p.y - p.x) > 2){
75                 burstballoons.push(orders[p.x][p.y] - 1);
76                 stk.push(new Pair(orders[p.x][p.y],p.y));
77                 stk.push(new Pair(p.x,orders[p.x][p.y]));
78             }
79         }
80         
81         while(!burstballoons.isEmpty()){
82             System.out.print(burstballoons.pop() + " ");
83         }
84     }
85 }

 

  代碼中還加入了記錄最后一個扎破的氣球的矩陣,用回溯法查詢這個矩陣即可找到一個求解的扎氣球的順序。求解算法包含三層循環,時間復雜度是O(n^3)。

 

 

 


免責聲明!

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



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