LeetCode 312
戳气球
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。如果你戳破气球 i ,就可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。
求所能获得硬币的最大数量。
解法1:回溯法(超时)
解题思路:
回溯法的思想就是选择一个气球来戳,然后进行递归戳下一个气球,当最底层的递归所有气球的戳完时,返回到上一层,选择另外一个气球来戳
class Solution {
int res = 0;
public int maxCoins(int[] nums) {
boolean[] breaked = new boolean[nums.length]; //表示一个气球是否被戳过
backwards(breaked,nums,0);
return res;
}
public void backwards(boolean[] breaked, int[] nums, int sum)
{
int left;
int right;
for(int i=0; i<nums.length; i++) //选择气球
{
if(breaked[i])
continue;
left=i-1;
right=i+1;
while(left>=0 && breaked[left]) //找左边的气球
left--;
while(right<nums.length && breaked[right]) //找右边的气球
right++;
left = (left==-1?1:nums[left]);
right = (right==nums.length?1:nums[right]);
breaked[i]=true;
backwards(breaked,nums,sum+(left*nums[i]*right)); //结果往下传
breaked[i]=false;
}
if(sum>res) //所有气球都戳完了,保存最大结果
res=sum;
}
}
使用递归确实要方便,但是同样存在大量的重复计算导致超时,比如说[7,9,8,0,7,1,3,5,5,2,3]
第一次递归
我们第一个戳破下标为0的气球,第二个戳破下标为1的气球
因此我们第三个气球的选择区间就是[8,0,7,1,3,5,5,2,3]
第二次递归
我们第一个戳破下标为1的气球,第二个戳破下标为0的气球
因此我们第三个气球的选择区间就是[8,0,7,1,3,5,5,2,3]
跟第一次是一样的,这就导致了大量的重复计算
时间复杂度达到了O(n^n),太夸张了
解法2:记忆化搜索
解题思路:
我们把戳气球的过程反过来看就是放气球的过程,所以我们用一个solve(left,right)函数来计算在这个区间能放满气球的最多硬币
具体过程就是
- 在整个区间找到一处i来放我们的气球得到
num=nums[left]*nums[i]*nums[right]
,然后该气球把区间分成两部分 - 然后最大值就是
solve(left,i-1)+num+solve(i+1,right)
,其中i是在(left+1,right)
中遍历得到的
代码如下:
class Solution {
public int[][] rec;
public int[] val;
public int maxCoins(int[] nums) {
int n = nums.length;
val = new int[n + 2];
for (int i = 1; i <= n; i++) {
val[i] = nums[i - 1];
}
val[0] = val[n + 1] = 1;
rec = new int[n + 2][n + 2];
for (int i = 0; i <= n + 1; i++) {
Arrays.fill(rec[i], -1);
}
return solve(0, n + 1);
}
public int solve(int left, int right) {
if (left >= right - 1)
//区间的长度为2,但是是开区间,左右两个气球已经占满位置了,所以气球没位置放了
{
return 0;
}
if (rec[left][right] != -1) {
return rec[left][right];
}
for (int i = left + 1; i < right; i++) {
int sum = val[left] * val[i] * val[right];
sum += solve(left, i) + solve(i, right);
rec[left][right] = Math.max(rec[left][right], sum);
}
return rec[left][right];
}
}
解法3:动态规划
解题思路:
动态规划的思想跟解法2基本相同,解法2是自顶向下,动态规划是自底向上
dp数组的含义是:在区间(i,j)能得到的最大硬币
因此动态转移方程就是
num = nums[i]*nums[k]*nums[j]
dp[i][j] = num+dp[i][k-1]+dp[k+1][j]的最大值
注意:k就是在(i+1,j)区间遍历的
代码如下:
class Solution {
public int maxCoins(int[] nums) {
int n = nums.length;
int[][] rec = new int[n + 2][n + 2];
int[] val = new int[n + 2];
val[0] = val[n + 1] = 1;
for (int i = 1; i <= n; i++) {
val[i] = nums[i - 1];
}
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 2; j <= n + 1; j++) {
for (int k = i + 1; k < j; k++) {
int sum = val[i] * val[k] * val[j];
sum += rec[i][k] + rec[k][j];
rec[i][j] = Math.max(rec[i][j], sum);
}
}
}
return rec[0][n + 1];
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/burst-balloons/solution/chuo-qi-qiu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。