leetcode312. 戳气球(动态规划-java)

265 篇文章 2 订阅
235 篇文章 0 订阅
本文介绍了LeetCode312题——戳气球,给出了暴力递归的解决方案,并加入了缓存优化,以及动态规划的解法。通过递归和动态规划寻找戳破气球获得硬币最大数量的策略,涉及边界处理和状态转移矩阵的构建。
摘要由CSDN通过智能技术生成

leetcode312. 戳气球

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/burst-balloons

题目描述

有 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 = 315 + 358 + 138 + 181 = 167

示例 2:
输入:nums = [1,5]
输出:10

提示:
n == nums.length
1 <= n <= 300
0 <= nums[i] <= 100

暴力递归

假设要打爆nums[L…R]这个范围上所有的气球,并且假设nums[L - 1]和nums[R + 1]的气球都没有被打爆,尝试的过程是process 函数,最后获得的最大分数为process(L,R).依次尝试,如果每个气球被打爆,具体为:
如果nums[L] 最后被打爆.也就是先把nums[L + 1,R]范围上的气球打完后,再打爆nums[L].先把nums[L + 1,R]上的气球打爆的最大分数为process(L + 1,R);因为此时nums[L + 1,R]上的气球都被打完了.所以nums[L] 左边是nums[L -1] ,右边是nums[R + 1],最后打爆L 气球的最大分数是:
nums[L - 1] * nums[L ] * nums[R + 1] + process(L + 1,R)

同理.如果nums[R]最后被打爆.可以得到最大分数是:
nums[L - 1] * nums[R] * nums[R + 1] + process(L,R- 1)

如果位于L 到 R 中间的某一个位置i被打爆i,那么就表示L 到 i-1位置和i+ 1 到R 位置上先被打爆,所以可以得出;最大分数是
process(L ,i - 1)+ process(i + 1,R) + nums[i] * nums[L -1] * nums[R + 1]

根据以上尝试方案,可以写出.暴力递归的尝试.

代码演示

 /**
     * 暴力递归 求L - R位置,的最大分数
     * 假设 L -1 位置和R + 1 位置没有被打爆.
     * @param nums
     * @param L
     * @param R
     * @return
     */
    public int process(int[] nums,int L,int R){
        //base case L 到 R 位置上只剩一个气球时,直接打爆
        if(L == R){
            return nums[L - 1] * nums[L] * nums[R + 1];
        }
        //最后打爆L 和最后打爆R 的情况,选择一个最大值
        int max = Math.max(nums[L - 1] * nums[L] * nums[R + 1] + process(nums,L+1,R),
                nums[L - 1] * nums[R] * nums[R + 1] + process(nums,L,R - 1));
        //L 到 R 之间的一个位置上,最大值
        for (int i = L + 1;i < R;i++){
            max = Math.max(max,
                    nums[L - 1] * nums[i] * nums[R + 1]
                + process(nums,L,i - 1)
                + process(nums,i + 1,R));
        }
        return max;
    }

如果对nums 开头和结尾补上1,那么两个1 之间的位置就是我们要求的的答案,这样可以避免边界的判断

  /**
     * 打气球
     * @param nums
     * @return
     */
    public int maxCoins(int[] nums) {
        if (nums == null || nums.length == 0){
            return 0;
        }
        if (nums.length == 1){
            return nums[0];
        }
        int N  = nums.length;
        int[]help = new int[N + 2];
        help[0] = 1;
        help[N + 1] = 1;
        for (int i = 0;i < N;i++){
            help[i + 1] = nums[i];
        }
        // 1 到 N 就是我们要的答案
        return process(help,1,N);
    }

暴力递归加缓存

    /**
     * 带缓存的递归
     * 记忆化搜索
     * @param nums
     * @return
     */
    public int maxCoins(int[] nums) {
        if (nums == null || nums.length == 0){
            return 0;
        }
        if (nums.length == 1){
            return nums[0];
        }
        int N  = nums.length;
        int[]help = new int[N + 2];
        help[0] = 1;
        help[N + 1] = 1;
        for (int i = 0;i < N;i++){
            help[i + 1] = nums[i];
        }
        int[][]dp = new int[N + 2][N + 2];
        return process(help,1,N,dp);
    }

    /**
     * 递归加缓存
     * @param nums
     * @param L
     * @param R
     * @param dp
     * @return
     */
    public int process(int[] nums,int L,int R,int[][]dp){
        if(L == R){
            return nums[L - 1] * nums[L] * nums[R + 1];
        }
        if(dp[L][R] != 0){
            return dp[L][R];
        }
        int max = Math.max(nums[L - 1] * nums[L] * nums[R + 1] + process(nums,L+1,R,dp),
                nums[L - 1] * nums[R] * nums[R + 1] + process(nums,L,R - 1,dp));
        for (int i = L + 1;i < R;i++){
            max = Math.max(max,
                    nums[L - 1] * nums[i] * nums[R + 1]
                + process(nums,L,i - 1,dp)
                + process(nums,i + 1,R,dp));
        }
        dp[L][R] = max;
        return max;
    }

动态规划

动态规划就是对递归的改写.三个步骤,
1,根据base case 初始化dp表
2.递归过程改成dp 表拿值得过程
3.返回递归调用得初始状态

/**
     * 动态规划
     * @param nums
     * @return
     */
    public int maxCoins2(int[]nums){
        if (nums == null || nums.length == 0){
            return 0;
        }
        if (nums.length == 1){
            return nums[0];
        }
        int N  = nums.length;
        int[]help = new int[N + 2];
        help[0] = 1;
        help[N + 1] = 1;
        for (int i = 0;i < N;i++){
            help[i + 1] = nums[i];
        }
        int[][]dp = new int[N + 2][N + 2];
        for (int i = 1;i <= N;i++){
            dp[i][i] = help[i - 1] * help[i] * help[i + 1];
        }
        for (int L = N;L >= 1;L--){
            for (int R = L + 1;R <= N;R++){
                int finalL = help[L - 1] * help[L] * help[R + 1] + dp[L + 1][R];
                int finalR = help[L - 1] * help[R] * help[R + 1] + dp[L][R - 1];
                dp[L][R] = Math.max(finalR,finalL);
                for (int i = L + 1;i < R;i++){
                    dp[L][R] = Math.max(dp[L][R],
                            help[L - 1] * help[i] * help[R + 1] +
                            dp[L][i - 1] + dp[i + 1][R]);
                }
            }
        }
        return dp[1][N];
    }

动态规划专题

leetcode63. 不同路径 II

leetcode62. 不同路径

leetcode877. 石子游戏

leetcode64. 最小路径和

leetcode416. 分割等和子集

leetcode354. 俄罗斯套娃信封问题

leetcode300. 最长递增子序列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值