LeetCode 刷题系列-312. 戳气球

题目

LeetCOde 传送门

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

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

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

说明:

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

示例:

输入: [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

解题思路

我们先定义开区间(i, j)表示只能戳破 ij 之间的气球,不能戳 ij。以示例为例,画出它的(0, 5)开区间图。

所以整个思路就是不管前面的气球怎么戳破的,只关注当前开区间内,最后戳破的气球是哪个。假设目前存在一个开区间(i, j)最后一个要戳破的气球是 k,此时的开区间图可以画成如下。
在这里插入图片描述
从这里可以看出,戳破 k 的时候,只与开区间的边界 ij(无法戳破)的气球有关,与开区间(i,k)(k,j)的戳破情况完全无关。所以我们的关注点只有最后一个戳破的气球

此时假设 dp[i][j] 表示开区间(i,j)内能获得的最大硬币数,那么(i,j)开区间得到的金币可以由 dp[i][k]dp[k][j]进行状态转移,若最后一个戳爆的气球为 k,能获得的硬币数:sum = dp[i][k] + val[i] * val[k] * val[j] + dp[k][j]

val[i]表示 i 位置气球的数字

最终状态转移方程如下:
在这里插入图片描述

一个小技巧, 令 val[i] = nums [i - 1],可以防止数组越界,减少边界条件处理。

代码实现

// 动态规划
var maxCoins = function (nums) {
     
  const len = nums.length;
  // 构造val数组,另val[i] = nums[i - 1]
  const val = new Array(len + 2);
  val[0] = 1;
  val[len + 1] = 1;
  for (let i = 1; i <= len; ++i) {
     
    val[i] = nums[i - 1];
  }

  // 初始化dp数组,其值全为0
  const dp = new Array(len + 2);
  for (let i = 0; i < len + 2; ++i) {
     
    dp[i] = new Array(len + 2).fill(0);
  }

  // 开区间的边界有一侧需要倒序遍历,否则无法覆盖所有区间,这里选择左侧倒序
  for (let i = len - 1; i >= 0; --i) {
     
    for (let j = i + 2; j <= len + 1; ++j) {
     
      for (let k = i + 1; k < j; ++k) {
     
        // 状态转移核心
        let sum = val[i] * val[k] * val[j];
        sum += dp[i][k] + dp[k][j];
        dp[i][j] = Math.max(dp[i][j], sum);
      }
    }
  }
  // 
  return dp[0][len + 1];
};

复杂度分析

时间复杂度: O ( n 3 ) O(n^3) O(n3),其中 n 是气球数量。状态数为 n 2 n^2 n2,状态转移复杂度为 O(n),最终复杂度为 O ( n 2 ∗ n ) = O ( n 3 ) O(n^2*n)=O(n^3) O(n2n)=O(n3)

空间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n 是气球数量。

你可能感兴趣的:(LeetCode系列,#,动态规划,leetcode,动态规划,算法)