动态规划:Burst Balloons

首先先明白什么是动态规划,引用百度百科的介绍:动态规划算法是五种常见的算法之一,通常用于求解具有某种最优性质的问题。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题。动态规划与其它算法相比,大大减少了计算量,丰富了计算结果,不仅求出了当前状态到目标状态的最优值,而且同时求出了到中间状态的最优值,这对于很多实际问题来说是很有用的。动态规划相比一般算法也存在一定缺点:空间占据过多,但对于空间需求量不大的题目来说,动态规划无疑是最佳方法!动态规划算法和贪婪算法都是构造最优解的常用方法。动态规划算法没有一个固定的解题模式,技巧性很强。

让我们用这道题来一起感受一下动态规划:(我也是第一次接触,所以有些地方可能说得不对,欢迎读者跟帖指出,谢谢)

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

Find the maximum coins you can collect by bursting the balloons wisely.

翻译如下:给定n个气球,索引从0到n-1。每个气球上涂上一些对它表示数组的数组。你被要求爆裂所有的气球。如果你爆气球我你会得到nums[左] *nums[我] *nums[右]硬币。在这里左和右是相邻的I指数后,爆发,左,右然后成为相邻。

大意比较明确,问题就是怎么来实现,当然,我们用动态规划的方法。引用经典的动态规划步骤:。具体地思路的思路如下。:

       1、构造问题所对应的过程。
       2、思考过程的最后一个步骤,看看有哪些选择情况。
       3、找到最后一步的子问题,确保符合“子问题重叠”,把子问题中不相同的地方设置为参数。
       4、使得子问题符合“最优子结构”。
       5、找到边界,考虑边界的各种处理方式。
       6、确保满足“子问题独立”,一般而言,如果我们是在多个子问题中选择一个作为实施方案,而不会同时实施多个方案,那么子问题就是独立的。
       7、考虑如何做备忘录。
       8、分析所需时间是否满足要求。
       9、写出转移方程式。

第一步,构造过程。这里我们是将一个数组中的数一个个地去除,然后将所得到的值加起来,所以过程就是去除一个元素然后得到值。

第二步,得最后一步。最后一步是将仅存的2个元素进行最后的取舍,留一个舍弃一个。当然,是保留对应的值更大的元素,舍弃较小元素。

第三步,最后一步的子问题。子问题是从仅存的2个元素中去掉哪一个,并将值加到总的值计算中。此时我们并不知道整个过程的子问题是什么,只能说暂时这样,留待后面继续分析。

第四步,使得子问题符合最后子结构。所谓“最优子结构”,是指如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。这时回顾我们上面说的关于对最后一步的分割,对2个元素的取舍,暂且可以认为满足。因为我们可以放心地使用我们认定的子结构的结果。

第五步,找到边界。边界为只有1种元素时,将该元素的值加到总的值上即可。

第六步,确保子结构独立。这里我们不得不寻找通用的子结构了(我认为这是最难的一步)。这里我们采用逆向的思路,假设最后一步剩了2个数,那么我们就要想,在不去除这个数的情况下,按照什么顺序去除其他元素才能获得最大值。再往上想一步,剩余3个数的时候,要按照什么顺序去除其他的数?依此类推。我知道这样想很抽象,所以我建议采用图辅助理解。

假设一共有4个元素。我们建立一个4 * 4的矩阵。对角线是第一次去除,矩阵上的值代表去除后的总值。假设右上角为最终结果,我们的分析是通过右上角回退到第一步,但实际操作还是要从第一步开始假设。但请记住,对于每一个问号值的求解,我们的分析都是:他之前的步骤是如何进行的才能得到问号最理想的值。

sums[4] = {3,1,5,8}

第一步的矩阵:

3    ?    ?    ?

0    15    ?    ?

0    0      40    ?

0    0      0       40

我们进行第一次分析,走一步。分析(1,2)(2,3)(3,4)3个问号的值。

以(1,2)为例,该点的值来源于(1,1)和(2,2),也就是说 ,这个点意味着我们前两次去除了0号(3)和1号(1)元素,只是顺序有所不同。3 -> ?意味着先去除0号元素再去除1号元素,15 -> ?意味着先去除1号再去除0号,其他位置也同理。比如(2,4)点意味着去除1,2和3号元素,只剩下0号元素没有去除。我们开始正式计算(1,2)的值。3到?的结果是3 + 5 = 8,而15到问号的值是15 + 15 = 30,30大于8,于是我们将(1,2)位置设为30。

(1,2)求出来,突然就觉得问题好像找到了方法。也就是说,对于任意的(i,j),他的值等于max{(i,j-1) + 对应情况下去掉第j-1号元素得到的值,(i – 1, j) + 对应情况下去掉j – 1号元素对应的值}。这样求下去,便可以求得最右上角的?的值,也就是我们要求的总值。

问题解决得有点突然,本来只是一步分析,误打误撞发现了通用的解题方法。(惭愧)

不管怎么说,问题已经打开了,我们列一个上述矩阵,凭借(i-1,j)和(i,j-1)来求的(i,j)的值,最右上角的值即为答案。

最后贴一下代码:

int maxCoins(vector<int>& nums) {
        int n = nums.size();
        nums.insert(nums.begin(), 1);
        nums.push_back(1);
        vector<vector<int>> dp(nums.size(), vector<int>(nums.size(), 0));
        for (int len = 1; len <= n; ++len)
            for (int left = 1; left <= n – len + 1; ++left) {
                int right = left + len – 1;
                for (int k = left; k <= right; ++k)
                    dp[left][right] = max(dp[left][right], nums[left-1]*nums[k]*nums[right+1] + dp[left][k-1] + dp[k+1][right]);
            }
        return dp[1][n];
    }

总体来说,动态规划算法,思路在于寻找子问题,借由子问题解决大问题,因为其对子问题数据的存储(矩阵可见),大大提高了效率。

第一次接触动态规划,写出来与大家分享一下,如有错误,请大家不吝赐教,谢谢!

    原文作者:动态规划
    原文地址: https://blog.csdn.net/Ray_sysu/article/details/61917847
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞