首先先明白什么是动态规划,引用百度百科的介绍:动态规划算法是五种常见的算法之一,通常用于求解具有某种最优性质的问题。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题。动态规划与其它算法相比,大大减少了计算量,丰富了计算结果,不仅求出了当前状态到目标状态的最优值,而且同时求出了到中间状态的最优值,这对于很多实际问题来说是很有用的。动态规划相比一般算法也存在一定缺点:空间占据过多,但对于空间需求量不大的题目来说,动态规划无疑是最佳方法!动态规划算法和贪婪算法都是构造最优解的常用方法。动态规划算法没有一个固定的解题模式,技巧性很强。
让我们用这道题来一起感受一下动态规划:(我也是第一次接触,所以有些地方可能说得不对,欢迎读者跟帖指出,谢谢)
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];
}
总体来说,动态规划算法,思路在于寻找子问题,借由子问题解决大问题,因为其对子问题数据的存储(矩阵可见),大大提高了效率。
第一次接触动态规划,写出来与大家分享一下,如有错误,请大家不吝赐教,谢谢!