Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >每日算法系列【LeetCode 312】戳气球

每日算法系列【LeetCode 312】戳气球

作者头像
godweiyang
发布于 2020-03-24 02:47:48
发布于 2020-03-24 02:47:48
64800
代码可运行
举报
文章被收录于专栏:算法码上来算法码上来
运行总次数:0
代码可运行

题目描述

有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。

求所能获得硬币的最大数量。

示例1

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
输入:
[3,1,5,8]
输出:
167
解释:
nums = [3,1,5,8] --> [3,5,8] -->   [3,8]   -->  [8]  --> []
coins =  3*1*5      +  3*5*8    +  1*3*8      + 1*8*1   = 167

提示

  • 你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
  • 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

题解

dfs+记忆化搜索

对于区间 [l, r] ,我们考虑最后一个被戳破的气球 k ,那么之前的步骤我们可以分为两步,也就是求 [l, k-1] 和 [k+1, r] 之间的最大分数。

那么为什么不考虑先戳破 k 呢?因为这样的话 [l, k-1] 和 [k+1, r] 就会连接在一起,两个子状态就不能独立计算了,互相会产生影响。

两个子区间的最大的分算完之后,最后 k 的得分就是 nums[l-1] * nums[k] * nums[r+1] ,取使得总得分最高的 k 就行了。

有一个小技巧就是,提示里也说了,就是刚开始的时候在首尾各添加一个分数为 1 的虚拟气球。

但是直接这样递归会超时,因为有很多的子状态都重复计算了,所以可以用一个全局的数组保存每个状态的分数。初始化为 -1 ,如果某个状态计算过了,就直接返回它的值就行了,不然就递归计算。

动态规划

上面的方法是自顶向下的,其实也可以转化成自底向上的,也就是从小的区间开始算起,最后算最大的,这就是动态规划的方法,具体的实现细节和上面是一模一样的。

代码

dfs+记忆化搜索(c++)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
public:
    static const int N = 510;
    int dp[N][N];

    int maxCoins(vector<int>& nums) {
        int n = nums.size();
        nums.insert(nums.begin(), 1);
        nums.push_back(1);
        memset(dp, -1, sizeof dp);
        int res = dfs(1, n, nums);
        return res;
    }

    int dfs(int l, int r, vector<int>& nums) {
        if (dp[l][r] != -1) return dp[l][r];
        if (l > r) return 0;
        int res = 0;
        for (int k = l; k <= r; ++k) {
            res = max(res, nums[l-1]*nums[k]*nums[r+1]+dfs(l, k-1, nums)+dfs(k+1, r, nums));
        }
        return dp[l][r] = res;
    }
};

动态规划(c++)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
public:
    static const int N = 510;
    int dp[N][N];

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

dfs+记忆化搜索(python)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        n = len(nums)
        nums = [1] + nums + [1]
        self.dp = [[-1]*(n+2) for _ in range(n+2)]
        res = self.dfs(1, n, nums)
        return res
    
    def dfs(self, l, r, nums):
        if self.dp[l][r] != -1:
            return self.dp[l][r]
        if l > r:
            return 0
        res = 0
        for k in range(l, r+1):
            res = max(res, nums[l-1]*nums[k]*nums[r+1]+self.dfs(l, k-1, nums)+self.dfs(k+1, r, nums))
        self.dp[l][r] = res
        return res

动态规划(python)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        n = len(nums)
        nums = [1] + nums + [1]
        dp = [[0]*(n+2) for _ in range(n+2)]
        for l in range(1, n+1):
            for i in range(1, n-l+2):
                j = i + l - 1
                for k in range(i, j+1):
                    dp[i][j] = max(dp[i][j], nums[i-1]*nums[k]*nums[j+1] + dp[i][k-1] + dp[k+1][j])
        return dp[1][n]

作者简介:godweiyang知乎同名华东师范大学计算机系硕士在读,方向自然语言处理深度学习。喜欢与人分享技术与知识,期待与你的进一步交流~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 算法码上来 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
LeetCode 312. 戳气球(DP,难)
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
Michael阿明
2021/02/20
5080
LeetCode 312. 戳气球(DP,难)
戳气球
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
你的益达
2020/08/05
1.2K0
一天一大 leet(戳气球)难度:困难-Day20200719
有 n 个气球,编号为 0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
前端小书童
2020/09/24
2490
一天一大 leet(戳气球)难度:困难-Day20200719
每日算法系列【LeetCode 115】不同的子序列
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)
godweiyang
2020/03/24
9260
LeetCode 312.戳气球
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
MashiroT
2023/03/14
3370
区间型动态规划题目解析
动态规划适用于有重叠子问题和最优子结构性质的问题。给定一个问题,如果可以将其划分为子问题,并解出其子问题,再根据子问题的解推导/递推以得出原问题的解。LeetCode上关于动态规划的题目众多,除了前述文章的最小路径、股票买卖等问题,区间型动态规划也是一类经典题目。本节将分析LeetCode上两道区间型动态规划题目。
用户6557940
2022/07/24
2430
区间型动态规划题目解析
算法刷题-戳气球(数组、动态规划)、Pow(x, n)(递归、数学)、编辑距离(字符串、动态规划)
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。 现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。 求所能获得硬币的最大数量。
共饮一杯无
2023/02/10
2490
Leetcode|312. 戳气球(反向思维+区间动态规划)
在求解问题前,考虑到作为状态的累计钱币数没有已知上限,是待求量。因此不能将累计钱币数作为dp索引,因此,我们要分析,这个问题能不能分解成小问题破解?显然,如果是按照先戳破第k个气球来思考,子问题之间是相互依赖的,问题只能分解,不能将小问题的解正确的合成。所以我们要做的是如何分解可以使得小问题之间相互独立?
SL_World
2021/09/18
5520
每日算法系列【LeetCode 329】矩阵中的最长递增路径
对于每个单元格,你可以往上,下,左,右四个方向移动。你不能在对角线方向上移动或移动到边界外(即不允许环绕)。
godweiyang
2020/03/24
1.1K0
经典动态规划:戳气球问题
今天我们要聊的这道题「Burst Balloon」和之前我们写过的那篇 经典动态规划:高楼扔鸡蛋问题 分析过的高楼扔鸡蛋问题类似,知名度很高,但难度确实也很大。因此 labuladong 公众号就给这道题赐个座,来看一看这道题目到底有多难。
labuladong
2021/09/23
9560
2021-04-29:给定一个数组 arr,代表一排有分数的气球。每打爆一个气球都能获得分数,假设打爆气 球 的分数为 X,获得
2021-04-29:给定一个数组 arr,代表一排有分数的气球。每打爆一个气球都能获得分数,假设打爆气 球 的分数为 X,获得分数的规则如下: 1)如果被打爆气球的左边有没被打爆的气球,找到离被打爆气球最近的气球,假设分数为 L;如果被打爆气球的右边有没被打爆的气球,找到离被打爆气球最近的气球,假设分数为 R。获得分数为 L*X*R。2)如果被打爆气球的左边有没被打爆的气球,找到离被打爆气球最近的气球,假设分数为 L;如果被打爆气球的右边所有气球都已经被打爆。获得分数为 L*X。3)如果被打爆气球的左边所有的气球都已经被打爆;如果被打爆气球的右边有没被打爆的 气球,找到离被打爆气球最近的气球,假设分数为 R;如果被打爆气球的右边所有气球都 已经 被打爆。获得分数为 X*R。4)如果被打爆气球的左边和右边所有的气球都已经被打爆。获得分数为 X。目标是打爆所有气球,获得每次打爆的分数。通过选择打爆气球的顺序,可以得到不同的总分,请返回能获得的最大分数。【举例】arr = {3,2,5} 如果先打爆3,获得3*2;再打爆2,获得2*5;最后打爆5,获得5;最后总分21 如果先打爆3,获得3*2;再打爆5,获得2*5;最后打爆2,获得2;最后总分18 如果先打爆2,获得3*2*5;再打爆3,获得3*5;最后打爆5,获得5;最后总分50 如果先打爆2,获得3*2*5;再打爆5,获得3*5;最后打爆3,获得3;最后总分48 如果先打爆5,获得2*5;再打爆3,获得3*2;最后打爆2,获得2;最后总分18 如果先打爆5,获得2*5;再打爆2,获得3*2;最后打爆3,获得3;最后总分19 返回能获得的最大分数为50。
福大大架构师每日一题
2021/08/05
3370
2021-04-29:给定一个数组 arr,代表一排有分数的气球。每打爆一
2021-04-29:给定一个数组 arr,代表一排有分数的气球。每打爆一个气球都能获得分数,假设打爆气 球 的分数为 X,获得分数的规则如下: 1)如果被打爆气球的左边有没被打爆的气球,找到离被打爆气球最近的气球,假设分数为 L;如果被打爆气球的右边有没被打爆的气球,找到离被打爆气球最近的气球,假设分数为 R。 获得分数为 L_X_R。 2)如果被打爆气球的左边有没被打爆的气球,找到离被打爆气球最近的气球,假设分数为 L;如果被打爆气球的右边所有气球都已经被打爆。获得分数为 L_X。 3)如果被打爆气球的左边所有的气球都已经被打爆;如果被打爆气球的右边有没被打爆的 气球,找到离被打爆气球最近的气球,假设分数为 R;如果被打爆气球的右边所有气球都 已经 被打爆。获得分数为 X_R。 4)如果被打爆气球的左边和右边所有的气球都已经被打爆。获得分数为 X。目标是打爆所有气球,获得每次打爆的分数。通过选择打爆气球的顺序,可以得到不同的总分,请返回能获得的最大分数。【举例】arr = {3,2,5} 如果先打爆3,获得3_2;再打爆2,获得2_5;最后打爆5,获得5;最后总分21 如果先打爆3,获得3_2;再打爆5,获得2_5;最后打爆2,获得2;最后总分18 如果先打爆2,获得3_2_5;再打爆3,获得3_5;最后打爆5,获得5;最后总分50 如果先打爆2,获得3_2_5;再打爆5,获得3_5;最后打爆3,获得3;最后总分48 如果先打爆5,获得2_5;再打爆3,获得3_2;最后打爆2,获得2;最后总分18 如果先打爆5,获得2_5;再打爆2,获得3_2;最后打爆3,获得3;最后总分19 返回能获得的最大分数为50。
福大大架构师每日一题
2021/05/04
3170
2021-04-29:给定一个数组 arr,代表一排有分数的气球。每打爆一
【区间 DP】热门区间 DP 运用题
有 n 个气球,编号为 0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
宫水三叶的刷题日记
2023/10/25
2030
【区间 DP】热门区间 DP 运用题
啊?HOT 100 中最难的题是游戏厂最爱
回溯属于「爆搜」方案,时间复杂度是指数级别的,必然会 TLE(超时),因此回溯做出来的解法不算通过哈。
宫水三叶的刷题日记
2023/12/18
2180
啊?HOT 100 中最难的题是游戏厂最爱
LeetCode312. 戳气球
 方法:动态规划,定义二维数组coins,coinsa表示把第a个气球和第b个气球之间(不包括a和b)的气球戳烂,最大能得到的分值
mathor
2018/07/24
1.1K0
LeetCode312. 戳气球
【算法/学习】:记忆化搜索
记忆化搜索(Memoization Search):是一种通过存储已经遍历过的状态信息,从而避免对同一状态重复遍历的搜索算法。
IsLand1314
2024/10/15
1330
【算法/学习】:记忆化搜索
用js分类刷leetcode3.动态规划(图文视频讲解)
动态规划,英文:Dynamic Programming,简称DP,将问题分解为互相重叠的子问题,通过反复求解子问题来解决原问题就是动态规划,如果某一问题有很多重叠子问题,使用动态规划来解是比较有效的。
js2030code
2022/12/07
6850
LeetCode 96,n个数构建BST的方法有多少种?
今天是LeetCode专题第62篇文章,我们一起来聊聊LeetCode的96题,Unique Binary Search Trees(唯一的二叉搜索树)。
TechFlow-承志
2020/08/17
2.1K0
记忆化搜索专题
大家好,又见面了,我是你们的朋友全栈君。   什么是记忆化搜索呢?搜索的低效在于没有能够很好地处理重叠子问题;动态规划虽然比较好地处理了重叠子问题,但是在有些拓扑关系比较复杂的题目面前,又显得无奈。记忆化搜索正是在这样的情况下产生的,它采用搜索的形式和动态规划中递推的思想将这两种方法有机地综合在一起,扬长避短,简单实用,在信息学中有着重要的作用。   用一个公式简单地说:记忆化搜索=搜索的形式+动态规划的思想。   动态规划:就是一个最优化问题,先将问题分解为子问题,并且对于这些分解的子问题自身就是最优的才能在这个基础上得出我们要解决的问题的最优方案,要不然的话就能找到一个更优的解来替代这个解,得出新的最优自问题,这当然是和前提是矛盾的。动态规划不同于 贪心算法,因为贪心算法是从局部最优来解决问题,而动态规划是全局最优的。用动态规划的时候不可能在子问题还没有得到最优解的情况下就做出决策,而是必须等待子问题得到了最优解之后才对当下的情况做出决策,所以往往动态规划都可以用 一个或多个递归式来描述。而贪心算法却是先做出一个决策,然后在去解决子问题。这就是贪心和动态规划的不同。 一般遇到一个动态规划类型的问题,都先要确定最优子结构,还有重叠子问题,这两个是动态规划最大的特征,然后就是要写 动态规划的状态方程,这个步骤十分十分的重要的,写动归方程是需要一定的经验的,这可以通过训练来达到目的。接着就是要自底向上的求解问题的,先将最小规模的子问题的最优解求出,一般都用一张表来记录下求得的解,到后来遇到同样的子问题的时候就可以直接查表得到答案,最后就是通过一步一步的迭代得出最后问题的答案了。 我的理解最重要的东西就是一定会要一个数组或者其他的存储结构存储得到的子问题的解。这样就可以省很多时间,也就是典型的空间换时间 动态规划的一种变形就是记忆化搜索,就是根据动归方程写出递归式,然后在函数的开头直接返回以前计算过的结果,当然这样做也需要一个存储结构记下前面计算过的结果,所以又称为记忆化搜索。 记忆化搜索递归式动态规划 1.记忆化搜索的思想 记忆化搜索的思想是,在搜索过程中,会有很多重复计算,如果我们能记录一些状态的答案,就可以减少重复搜索量 2、记忆化搜索的适用范围 根据记忆化搜索的思想,它是解决重复计算,而不是重复生成,也就是说,这些搜索必须是在搜索扩展路径的过程中分步计算的题目,也就是“搜索答案与路径相关”的题目,而不能是搜索一个路径之后才能进行计算的题目,必须要分步计算,并且搜索过程中,一个搜索结果必须可以建立在同类型问题的结果上,也就是类似于动态规划解决的那种。 也就是说,他的问题表达,不是单纯生成一个走步方案,而是生成一个走步方案的代价等,而且每走一步,在搜索树/图中生成一个新状态,都可以精确计算出到此为止的费用,也就是,可以分步计算,这样才可以套用已经得到的答案 3、记忆化搜索的核心实现 a. 首先,要通过一个表记录已经存储下的搜索结果,一般用哈希表实现 b.状态表示,由于是要用哈希表实现,所以状态最好可以用数字表示,常用的方法是把一个状态连写成一个p进制数字,然后把这个数字对应的十进制数字作为状态 c.在每一状态搜索的开始,高效的使用哈希表搜索这个状态是否出现过,如果已经做过,直接调用答案,回溯 d.如果没有,则按正常方法搜索 4、记忆化搜索是类似于动态规划的,不同的是,它是倒做的“递归式动态规划”。
全栈程序员站长
2022/09/19
6940
记忆化搜索专题
每日算法系列【LeetCode 239】滑动窗口最大值
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
godweiyang
2020/03/24
7780
推荐阅读
相关推荐
LeetCode 312. 戳气球(DP,难)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验