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