忽然明白动态规划怎么做了

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];
    }
};

问题成功解决~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值