963 字
5 分钟
戳气球

题目描述#

有 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. 动态规划(区间DP)
    • 定义 dp[i][j] 为戳破区间 (i, j) 内气球能获得的最大硬币数
    • 区间开区间处理,不包含边界气球
  2. 倒序思考
    • 考虑区间内最后戳破的气球 k
    • 戳破 k 时获得 nums[i]*nums[k]*nums[j] 硬币
  3. 状态转移
    • dp[i][j] = max(dp[i][k] + dp[k][j] + nums[i]*nums[k]*nums[j])
    • 枚举所有可能的 k
  4. 边界处理
    • 在数组两端添加虚拟气球 [1, ..., 1]
    • 初始化长度为0的区间值为0

关键洞察#

  1. 动态规划(区间DP)
    • 定义 dp[i][j] 为戳破区间 (i, j) 内气球能获得的最大硬币数
    • 区间开区间处理,不包含边界气球
  2. 倒序思考
    • 考虑区间内最后戳破的气球 k
    • 戳破 k 时获得 nums[i]*nums[k]*nums[j] 硬币
  3. 状态转移
    • dp[i][j] = max(dp[i][k] + dp[k][j] + nums[i]*nums[k]*nums[j])
    • 枚举所有可能的 k
  4. 边界处理
    • 在数组两端添加虚拟气球 [1, ..., 1]
    • 初始化长度为0的区间值为0

复杂度分析#

指标说明
时间复杂度O(n³):三重循环(长度、起点、分割点)
空间复杂度O(n²):二维DP表存储区间最优解
最优情况O(n³):无法低于立方复杂度

代码实现#

区间 DP

const maxCoins = (nums) => {
const n = nums.length;
// 添加首尾虚拟气球
const balloons = [1, ...nums, 1];
const m = balloons.length;
// 初始化DP表
const dp = Array.from({ length: m }, () => Array(m).fill(0));
// 按区间长度枚举
for (let len = 2; len < m; len++) {
for (let i = 0; i < m - len; i++) {
const j = i + len;
// 枚举最后戳破的气球k
for (let k = i + 1; k < j; k++) {
const coins = dp[i][k] + dp[k][j] +
balloons[i] * balloons[k] * balloons[j];
dp[i][j] = Math.max(dp[i][j], coins);
}
}
}
return dp[0][m - 1];
};

回溯

/**
* @param {number[]} nums
* @return {number}
*/
var maxCoins = function (nums) {
const n = nums.length;
const visited = Array.from({ length: n }, () => []);
const helper = (begin, end) => {
if (begin > end) {
return 0;
}
if (visited[begin][end]) {
return visited[begin][end]
}
let ans = 0;
for (let i = begin; i <= end; i++) {
let cur = nums[i];
const left = helper(begin, i - 1);
const right = helper(i + 1, end);
if (begin - 1 >= 0) cur *= nums[begin - 1];
if (end + 1 < n) cur *= nums[end + 1]
ans = Math.max(ans, left + right + cur)
}
visited[begin][end] = ans;
return ans;
}
return helper(0, n - 1);
};

实际应用场景#

  1. 最优拆除规划:建筑物拆除顺序优化(相邻建筑影响)`
  2. 化学链反应:分子链断裂能量最大化计算`
  3. 游戏策略:泡泡龙类游戏的最佳消除顺序`
  4. 生产流程:工序依赖型生产线的任务排序`

变种:输出最优戳破顺序#

const maxCoinsWithPath = (nums) => {
const balloons = [1, ...nums, 1];
const n = balloons.length;
const dp = Array.from({ length: n }, () => Array(n).fill(0));
const path = Array.from({ length: n }, () => Array(n).fill(-1));
for (let len = 2; len < n; len++) {
for (let i = 0; i < n - len; i++) {
const j = i + len;
for (let k = i + 1; k < j; k++) {
const coins = dp[i][k] + dp[k][j] + balloons[i] * balloons[k] * balloons[j];
if (coins > dp[i][j]) {
dp[i][j] = coins;
path[i][j] = k; // 记录最后戳破的位置
}
}
}
}
// 回溯重建路径
const order = [];
const buildPath = (i, j) => {
if (i + 1 === j) return;
const k = path[i][j];
order.push(balloons[k]);
buildPath(i, k);
buildPath(k, j);
};
buildPath(0, n - 1);
return { coins: dp[0][n - 1], order };
};

相似题目#

戳气球
https://website-truelovings-projects.vercel.app/posts/algorithms/戳气球/
作者
欢迎来到StarSky的网站!
发布于
2025-08-24
许可协议
CC BY-NC-SA 4.0