https://leetcode-cn.com/problems/burst-balloons/
戳气球问题。
题目让你戳这些[3,1,5,8]气球。可是气球数目太多了,能不能削减一下数量呢?让我戳[3, 1, 5]或者[1, 5, 8]或者[3]也可以……总之气球数目越少越好。
上述列举的气球数目较少的情况就是子问题,也可以说是一个个的“状态”(我认为状态就是通过几个变量来表示一个问题)。动态规划其实就是一个状态经过选择之后可由其他状态表示嘛。如何表示这些状态?凭空想很难,但是仔细看题型,有点想序列问题。我们这样定义:两个指针i、j的位置就是一个状态。当i指向3,j指向8时,我们就要解决[3, 1, 5, 8]这个问题;当i指向1,j指向8时,我们就要解决[1, 5, 8]这个子问题……
我们现在要解决一个“状态”,也就是一个问题,怎么解决?穷举呗。我在[i……j]里面随便戳一个,然后继续戳……最后总能算出一个结果;之后再从头开始,戳另一个……。动态规划其实就是聪明的穷举。我要戳气球k,戳完之后呢?再去戳[i……k - 1]和[k + 1, j]。[i……k - 1]和[k + 1, j]这不就是两个“状态”吗?如果我们这两个子问题已经提前解决了,不就可以直接求出一个结果来了吗?
那么两个子问题的结果保存在哪里呢?我们建一个数组吧,dp[i][j]表示将[i……j]气球戳破所获得的最大硬币数量。所以要解决i、j这一个”状态“(这个”状态“就是一个问题的描述),就很自然地写出解决方案:
dp[i][j] = max(dp[i][j], dp[i][k - 1] + dp[k + 1][j] + nums[k - 1] * nums[k] * nums[k + 1])。(i<=k<=j)
不过,解决方法不只一种。再来看这个题,因为要涉及nums[left] 和nums[right],当left和right越界怎么办?还是靠做题的经验,建立两个哨兵。也就是把nums[-1]和nums[n]加入nums中。状态也有所变化,i、j还是一个状态,它的含义是:把(i……j)区间里的气球戳破。也就是把闭区间变成了开区间。既然建立了哨兵,就要好好利用它啊。我们还是解决一个“状态",比如就解决题目所给的问题。怎么解决?同样是穷举k,但这时候我们是先戳破(i, k)的气球,再戳破(k, j)的气球,最后戳破k。这样做有什么好处?写出状态转移方程就会发现好处了:
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j]);
一个状态可以由另外的状态推出,那么是不是存在最简单的状态,自底向上,可以推出所有的状态?肯定有的。这个最简单的状态就是 j == i和i - j == 1。此时dp[i][[j] = 0。现在画一个表格,填上最简单的状态。
对某一个状态i、j,比如说1、5,我们解决这个”状态“需要知道那些”状态“?
因此我们进行遍历的时候,可以斜着遍历,或者i从下到上,j从左到右。我们取后者,附上代码:
class Solution {
public:
int maxCoins(vector<int>& nums) {
nums.insert(nums.begin(), 1);
nums.push_back(1);
int dp[nums.size() + 5][nums.size() + 5]; //dp[i][j]表示将气球(i……j)戳破所获得硬币的最大数量
for (int i = 0; i <= nums.size(); i ++) {
for (int j = 0; j < nums.size(); j ++) {
dp[i][j] = 0;
}
}
for (int i = nums.size() - 2; i >= 0; i --) {
for (int j = i + 2; j < nums.size(); j ++) {
for (int k = i + 1; k < j; k ++) {
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j]);
}
}
}
return dp[0][nums.size() - 1];
}
};
问题成功解决~