312. 戳气球 (Hard)
问题描述
有 n 个气球,编号为 0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i
相邻的两个气球的序号。如果 i - 1 或 i + 1 超出了数组的边界,那么就当它是一个数字为 1
的气球。
求所能获得硬币的最大数量。
示例 1:
输入:nums = [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
示例 2:
输入:nums = [1,5]
输出:10
提示:
n == nums.length1 <= n <= 3000 <= nums[i] <= 100
解题思路
分治法+记忆化搜索
首先在数组的首尾各插入元素1,以便于计算;
我们可以考虑将这个问题划分成两个不存在相互依赖的子问题,例如考虑[1, 3, 1, 5, 8, 1](注意首尾的1是后来单独插入的),这里我们考虑,假设最后一个戳爆的气球是5,那么该问题就可以划分成戳[1, 3, 1, 5]和戳[5, 8, 1]两组气球的得分再加上最后戳爆气球5的得分,(注意这里数组中的第一个和最后一个元素实际上都不是气球的元素,不能戳)。
从递归的角度来说,有dfs(nums, left, right) = max(dfs(nums, left, mid) + dfs(nums, mid, right) + nums[mid] * nums[l] * nums[r]);但是这样直接递归必然导致超时,因此我们考虑采用一个数组cach[left][right]来保存dfs(nums, left, right)的结果,如果cach[left][right]不为0,说明dfs(nums, left, right)已经计算过了,不需要再重复计算了,直接return cach[left][right]就好了。
动态规划
由上面的记忆化搜索,其实可以非常方便地改写成动态规划的形式,即
dp[left][right] = max(dp[left][right], dp[left][mid] + dp[mid][right] + nums[left] * nums[mid] * nums[right]);
这里状态dp[left][right]的定义为:开区间(left, right)引爆气球所能获得的最大分数,mid表示的是最后一个引爆的气球的坐标;
因此,这里要注意状态转移方程遍历的顺序。
代码
分治法+记忆化搜索
class Solution {
public:
保证子问题之间不存在依赖, 分治:递归搜索+保存计算结果
int dfs(vector<int> &nums, int left, int right, vector<vector<int>> &cach) {
if (left >= right) {
return 0;
}
int maxnum = 0;
for (int i = left + 1; i < right; i++) {
if (cach[left][right] > 0)
return cach[left][right];
else
maxnum = std::max(maxnum, nums[left] * nums[i] * nums[right] + dfs(nums, left, i, cach) + dfs(nums, i, right, cach));
}
cach[left][right] = maxnum;
return maxnum;
}
int maxCoins(vector<int> &nums) {
nums.insert(nums.begin(), 1);
nums.push_back(1);
int n = nums.size();
vector<vector<int>> cach(n, vector<int>(n, 0));
return dfs(nums, 0, n - 1, cach);
}
}
动态规划
#include <vector>
using std::vector;
class Solution {
public:
int maxCoins(vector<int> &nums) {
nums.insert(nums.begin(), 1);
nums.push_back(1);
int n = nums.size();
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int l = n - 1; l >= 0; l--) { // 注意这里的遍历顺序!
for (int r = 0; r < n; r++) {
for (int mid = l + 1; mid < r; mid++) {
dp[l][r] = max(dp[l][r], dp[l][mid] + dp[mid][r] + nums[mid] * nums[l] * nums[r]);
}
}
}
return dp[0][n - 1];
}
};
错误示范
class Solution {
public:
int dfs(vector<int> &nums, set<int> &rset, set<int, std::greater<int>> &lset, vector<vector<vector<int>>> &cach, int n) {
if (rset.size() == 2)
return 0;
int maxnum = 0; //应该说递归,转化为子问题的时候就出问题了,完全变成回溯了
for (int i = 1; i <= n; i++) {
auto iter = rset.find(i);
if (iter != rset.end()) {
int idx_left = *lset.upper_bound(i);
int idx_right = *rset.upper_bound(i);
if (cach[i][idx_left][idx_right] > 0) {
maxnum = std::max(maxnum, cach[i][idx_left][idx_right]);
} else {
rset.erase(i);
lset.erase(i);
cach[i][idx_left][idx_right] = nums[i] * nums[idx_left] * nums[idx_right] + dfs(nums, rset, lset, cach, n);
maxnum = std::max(maxnum, cach[i][idx_left][idx_right]);
rset.insert(i);
lset.insert(i);
}
}
}
return maxnum;
}
int maxCoins(vector<int> &nums) {
set<int> rset;
set<int, std::greater<int>> lset;
int n = nums.size();
nums.insert(nums.begin(), 1);
nums.push_back(1);
for (int i = 0; i < nums.size(); i++) {
rset.insert(i);
lset.insert(i);
}
vector<vector<vector<int>>> cach(nums.size(), vector<vector<int>>(nums.size(), vector<int>(nums.size(), -1)));
return dfs(nums, rset, lset, cach, n);
}
};
这份代码,如果去掉保存计算结果的数组,就变成暴力回溯了,那么答案正确,但是会超时,加上保存计算结果的数组之后,答案则会出现错误,cach[i][left][right] = nums[i] * nums[idx_left] * nums[idx_right] + dfs(nums, rset, lset, cach, n);从表面含义来看,是在当前气球排列为left, i, right的情况下,戳爆i所能获得的最大硬币数,问题在于,这样写的到的结果是[0,1,2], [1,2,3], [2,3,4]...[i - 1, i, rihgt]中的最大值。
312. 戳气球 (Hard)的更多相关文章
- Java实现 LeetCode 312 戳气球
312. 戳气球 有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中. 现在要求你戳破所有的气球.每当你戳破一个气球 i 时,你可以获得 nums[left ...
- Leetcode 312.戳气球
戳气球 有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中. 现在要求你戳破所有的气球.每当你戳破一个气球 i 时,你可以获得 nums[left] * n ...
- 312. 戳气球【困难】【区间DP】
题目链接 有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中. 现在要求你戳破所有的气球.每当你戳破一个气球 i 时,你可以获得 nums[left] * ...
- LeetCode 312. Burst Balloons(戳气球)
参考:LeetCode 312. Burst Balloons(戳气球) java代码如下 class Solution { //参考:https://blog.csdn.net/jmspan/art ...
- 312 Burst Balloons 戳气球
现有 n 个气球按顺序排成一排,每个气球上标有一个数字,这些数字用数组 nums 表示.现在要求你戳破所有的气球.每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * ...
- [Swift]LeetCode312. 戳气球 | Burst Balloons
Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by ...
- Q312 戳气球
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中. 现在要求你戳破所有的气球.每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[ ...
- leetcode 戳气球
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中. 现在要求你戳破所有的气球.每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[ ...
- Burst Balloons(leetcode戳气球,困难)从指数级时间复杂度到多项式级时间复杂度的超详细优化思路(回溯到分治到动态规划)
这道题目做了两个晚上,发现解题思路的优化过程非常有代表性.文章详细说明了如何从回溯解法改造为分治解法,以及如何由分治解法过渡到动态规划解法.解法的用时从 超时 到 超过 95.6% 提交者,到超过 9 ...
- leetcode312 戳气球
动态规划 time O class Solution { public: int maxCoins(vector<int>& nums) { nums.insert(nums.be ...
随机推荐
- [CSS]背景图片很大,根据屏幕缩小适配后,div之间有空隙的问题
RT.美术给的素材宽度是1080px的. 在不缩放的情况下,1080px宽度的屏幕显示div之间正常,没有空隙,但使用transform属性之后,div缩小,div之间有空隙(白线) 百度有人说给这些 ...
- vue中vue2-google-maps使用谷歌地图的基础操作
小哥我最近使用谷歌地图做了一个项目,于是乎各种坑就扑面而来,未免下次接着踩坑特留下自己的爬坑记录. 首先我是没用过谷歌地图也不知道靠谱不靠谱,于是乎傻傻的入坑了, 1.首先你要是没有vpn(或者fq工 ...
- huawei--配置链路聚合
huawei--配置链路聚合 项目要求: LSW1和LSW2之间配置链路聚合,链路聚合分为手工和lacp两种模式.配置完后查看链路聚合状态. 项目实施: (vlan10 20 30的创建命令vlan ...
- SAR成像(六):距离徙动矫正
1.什么是距离徙动? 距离徙动产生原因是合成孔径雷达的运动平台与目标之间的相对运动.对于地面一静止的点目标,当它处于雷达波束照射范围内的时间中时,随着飞行载体的运动,它与雷达飞行载体之间的距离会不断的 ...
- C++ 手动实现队列(queue) (课后作业版)
#include <iostream> using namespace std; template <typename T> class Queue { public: Que ...
- IO流(1)
IO流(1) 目录 IO流(1) 文件 创建文件 获取文件信息 目录的操作和文件删除 文件 文件流 文件在程序中以流的形式来操作 输入流:数据从数据源(文件)到程序(内存)的路径 输出流:数据从程序( ...
- 多个if...else和switch...case语句的区别和分析
1.场景: 当我们有一个判断条件的时候,显然用if语句比较方便有效. 但当判断条件很多的时候,我们可以使用if语句或者if....eles 语句和switch case 语句. 2.如何选择 一般情 ...
- OS-lab5
OS-lab5 磁盘管理 完成文件系统的第一步就是要能够处理磁盘等外设的信息. lib/syscall_all.c 处理磁盘的信息,最基本的就是对磁盘进行读写操作. sys_write_dev函数用于 ...
- Python的入门学习Day 19~21——form”夜曲编程“
..Day 19 time:2021.8.16. 很神奇地,编程和数学渐渐有了交集--今天学习了函数,连模样都像 f(x) 的形式.比较有区分度的是,在P里,"x"被称为&qu ...
- SQL CASE 标注
根据 状态值 显示中文备注 case when a.zht='0' then '录入' when a.zht='1' then '待审核' when a.zht='2' then '已审核' end ...