LeetCode(312):戳气球 Burst Balloons(Java)

234 篇文章 1 订阅
177 篇文章 0 订阅

2019.7.23 #程序员笔试必备# LeetCode 从零单刷个人笔记整理(持续更新)

这道智力题一下子看起来让人无从下手,因为每个气球本身的分值取决于相邻气球的分值之和,而戳破后,气球之间的相邻状态会发生改变。那么对于不同的n个戳气球的选择,会生成n个不同的结果,实质反映会是一棵深度为n,结点的度从n开始逐层减少的决策树。

那么其实可以想到一种递归的做法,但是很明显,当n比较大的时候会超时。这题正解是动态规划:

自顶向下:

我们可以逆向思考:拿出一个气球作为最后一个点爆的气球,则该气球左边和右边的气球分别是两个独立的子问题。

对于在某个区间[begin, end]中的独立子问题,可以对该区间内的所有气球i进行遍历,计算每一个气球作为最后一个气球时,区间能够获得硬币的最大数量

最后一个气球时获得硬币的数量 = 左区间最大数量 + 右区间最大数量 + nums[i] * nums[begin-1] * nums[end+1](因为计算i时,区间内已经没有气球,取区间两端)

//begin <= loc <= end
dp[begin][end] = dp[begin][loc - 1] + dp[loc + 1][end] + nums[begin - 1] * nums[loc] * nums[end + 1]

将遍历所有气球后的最大值更新到dp[begin][end],表示该区间从第begin个气球到第end个气球中能够获得硬币的最大数量。就可以愉快地开始dp了。

这里涉及到两个tips:1.为原数组两段添加1,方便计算区间端点;2.跳过已经更新过的区间。

自底向上:

自顶向下的方法涉及到重复区间的运算,需要通过不断跳过已经计算的区间来完成剪枝,而自底向上的方法可以规避掉这一点。

自底向上的思路是:先计算每个气球单独的情况,再拓展区间。状态转移方程变为:

//begin <= loc <= end
dpscore[begin][end] = Math.max(dpscore[begin][end], dpscore[begin][loc - 1] + dpscore[loc + 1][end] + list.get(loc) * list.get(begin - 1) * list.get(end + 1));

最外层循环(大区间):从区间长度len = 1开始更新,到区间长度为全长。

中间层循环(大区间右侧固定的子区间):从区间起点begin=1开始更新,到区间长度len,对应区间终点end为begin+len-1。

最内层循环(子区间固定):从区间起点loc = begin,到终点end,更新loc,并计算dpscore值。


传送门:戳气球

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

Find the maximum coins you can collect by bursting the balloons wisely.

Note:

You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

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

现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 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


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;


/**
 *
 * Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums.
 * You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins.
 * Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.
 * Find the maximum coins you can collect by bursting the balloons wisely.
 * Note:
 * You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
 * 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
 * 有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
 * 现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。
 * 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。
 * 求所能获得硬币的最大数量。
 * 说明:
 * 你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
 * 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
 *
 */

//https://www.bilibili.com/video/av45180542
public class BurstBalloons {

    public static void main(String[] args){
        int[] nums = {7,9,8,0,7,1,3,5,5,2,3};
        System.out.println(maxCoins2(nums));
    }

    //回溯法(会超时)
    public int score;
    public int maxCoins(int[] nums) {
        if(nums.length == 0)
            return 0;
        score = 0;
        Solution(nums, 0);
        return score;
    }

    public void Solution(int[] nums, int coins){
        if(nums.length == 0){
            if(coins > score){
                score = coins;
            }
            return;
        }

        for(int i = 0; i < nums.length; i++){
            int delta = nums[i] * (i - 1 < 0 ? 1 : nums[i - 1]) * (i + 1 > nums.length - 1 ? 1 : nums[i + 1]);
            int[] newnum = new int[nums.length - 1];
            System.arraycopy(nums, 0, newnum, 0, i);
            System.arraycopy(nums, i + 1, newnum, i, nums.length - i - 1);
            Solution(newnum, coins + delta);
        }
    }

    //动态规划
    //逆向思考:拿出一个气球作为最后一个点爆的气球,则该气球左边和右边的气球分别是两个独立的子问题
    //拓展到某个区间[begin, end],对该区间内的所有气球i进行遍历,计算每一个气球作为最后一个气球时,区间能够获得硬币的最大数量
    //最后一个气球时获得硬币的数量 = 左区间最大数量 + 右区间最大数量 + nums[i] * nums[begin-1] * nums[end+1](因为计算i时,区间内已经没有气球,取区间两端)
    //将遍历所有气球后的最大值更新到dp[begin][end],表示该区间从第begin个气球到第end个气球中能够获得硬币的最大数量
    public static int[][] dp;

    public static int maxCoins1(int[] nums) {
        if(nums.length == 0)
            return 0;

        //将nums转换为ArrayList,并在前后位置1
        Integer[] intnum = Arrays.stream(nums).boxed().toArray(Integer[]::new);
        ArrayList<Integer> numslist = new ArrayList<>(Arrays.asList(intnum));
        numslist.add(0, 1);
        numslist.add(1);

        //将dp初始化为(n+2)*(n+2)的数组
        dp = new int[nums.length + 2][nums.length + 2];


        return Solution1(numslist, 1, nums.length);
    }

    public static int Solution1(ArrayList<Integer> numslist, int begin, int end){
        if(begin > end)
            return 0;
        //不对已更新的区间进行二次更新
        if(dp[begin][end] > 0)
            return dp[begin][end];

        //dp[begin][end]是从第begin个气球到第end个气球中能够获得硬币的最大数量
        //dp[begin][end] = dp[begin][loc - 1] + dp[loc + 1][end] + nums[begin - 1] * nums[loc] * nums[end + 1](begin <= loc <= end)
        for(int i = begin; i <= end; i++){
            int left = Solution1(numslist, begin, i - 1);
            int right = Solution1(numslist, i + 1, end);
            int delta = numslist.get(i) * numslist.get(begin - 1) * numslist.get(end + 1);
            dp[begin][end] = Math.max(dp[begin][end], left + right + delta);
        }

        return dp[begin][end];
    }

    //动态规划改进版
    //自底向上思考,先计算每个气球单独的情况,再拓展区间,避免递归与重复计算dpscore值
    public static int maxCoins2(int[] nums){
        int size = nums.length;
        if(size <= 0)
            return 0;
        int[][] dpscore = new int[size + 2][size + 2];

        Integer[] intnums = Arrays.stream(nums).boxed().toArray(Integer[]::new);
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(intnums));
        list.add(0, 1);
        list.add(1);

        //最外层循环(大区间):从区间长度len = 1开始更新,到区间长度为全长
        for(int len = 1; len <= size; ++len){
            //中间层循环(大区间右侧固定的子区间):从区间起点begin=1开始更新,到区间长度len,对应区间终点end为begin+len-1。
            //由此需要end<=size对应最后一个气球
            for(int begin = 1; begin + len - 1 <= size; ++begin){
                int end = begin + len - 1;
                //在区间[begin, end]内更新每一个dpscore值
                for(int loc = begin; loc <= end; ++loc) {
                    dpscore[begin][end] = Math.max(dpscore[begin][end], dpscore[begin][loc - 1] + dpscore[loc + 1][end] + list.get(loc) * list.get(begin - 1) * list.get(end + 1));
                }
            }
        }

        return dpscore[1][size];
    }
}




#Coding一小时,Copying一秒钟。留个言点个赞呗,谢谢你#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值