LeetCode 312. 戳气球(动态规划解决)

截止到目前我已经写了 500多道算法题,其中部分已经整理成了pdf文档,目前总共有1000多页(并且还会不断的增加),大家可以免费下载
下载链接https://pan.baidu.com/s/1hjwK0ZeRxYGB8lIkbKuQgQ
提取码:6666

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

比如上图中3和5本来是不相邻的,但当我们戳破1的时候,3和5变成了相邻,所以我们可以使用一个list把每个气球的值都存起来,戳破的时候就把他从list中给删除。来看下代码

public int maxCoins(int[] nums) {
    
    
    List<Integer> list = new LinkedList<>();
    //先把nums数组中的元素放到list中
    for (int num : nums) {
    
    
        list.add(num);
    }
    return helper(list);
}

public int helper(List<Integer> list) {
    
    
    //如果是空,则表示没有气球了,直接返回0
    if (list.isEmpty())
        return 0;
    int max = 0;
    int size = list.size();
    for (int i = 0; i < size; ++i) {
    
    
        //戳破当前气球所获得的硬币数量
        int sum = 0;
        if (size == 1) {
    
    
            //如果只有一个气球,直接戳破他获取的硬币就是
            //当前气球上的数字
            sum = list.get(i);
        } else if (size == 2) {
    
    
            //如果有两个气球,戳破任何一个气球都是他俩的乘积
            sum = list.get(0) * list.get(1);
        } else {
    
    
            //如果是3个和3个以上的气球要注意戳破第1个和最后一个气球
            //的边界条件
            if (i == 0) {
    
    
                sum = list.get(i) * list.get(i + 1);
            } else if (i == size - 1) {
    
    
                sum = list.get(i) * list.get(i - 1);
            } else {
    
    
                sum = list.get(i - 1) * list.get(i) * list.get(i + 1);
            }
        }
        //把当前气球戳破了,就把他从list中移除
        Integer num = list.remove(i);
        //记录最大的值,sum表示戳破当前气球所得到的硬币数,
        //helper(list)表示戳破剩下的气球所获得的硬币数
        max = Math.max(max, sum + helper(list));
        //把当前气球添加进list,尝试戳破下一个气球
        list.add(i, num);
    }
    return max;
}

在这里插入图片描述

public int maxCoins(int[] nums) {
    
    
    int length = nums.length;
    //申请一个比原来长度大2的临时数组
    int[] temp = new int[length + 2];
    //临时数组的首尾值赋为1,中间的值和nums的值一样
    for (int i = 1; i <= length; i++)
        temp[i] = nums[i - 1];
    temp[0] = temp[length + 1] = 1;
    return helper(temp, 0, length + 1);
}

//戳破开区间(left,right)中所有气球所获得的最大硬币
public int helper(int[] temp, int left, int right) {
    
    
    //如果(left,right)之间没有气球,返回0,表示没有任何气球可戳破
    if (left + 1 == right)
        return 0;
    int res = 0;
    //在(left,right)中我们尝试戳破每一个位置的气球,取最大值即可
    for (int i = left + 1; i < right; ++i) {
    
    
        int sum = temp[left] * temp[i] * temp[right] + helper(temp, left, i) + helper(temp, i, right);
        res = Math.max(res, sum);
    }
    return res;
}

这样当数据量比较大的时候还是会超时,因为这里包含了大量的重复计算,我们还可以使用一个map,把计算的结果存到map中,下次使用的时候如果计算过了,就直接从map中取,来看下代码

public int maxCoins(int[] nums) {
    
    
    int length = nums.length;
    //申请一个比原来长度大2的临时数组
    int[] temp = new int[length + 2];
    //临时数组的首尾值赋为1,中间的值和nums的值一样
    for (int i = 1; i <= length; i++)
        temp[i] = nums[i - 1];
    temp[0] = temp[length + 1] = 1;
    int[][] map = new int[length + 2][length + 2];
    return helper(map, temp, 0, length + 1);
}

public int helper(int[][] map, int[] temp, int left, int right) {
    
    
    //如果(left,right)之间没有气球,返回0,表示没有任何气球可戳破
    if (left + 1 == right)
        return 0;
    //如果已经计算过了,就直接从map中取
    if (map[left][right] > 0)
        return map[left][right];
    int res = 0;
    for (int i = left + 1; i < right; ++i) {
    
    
        int sum = temp[left] * temp[i] * temp[right] + helper(map, temp, left, i) + helper(map, temp, i, right);
        res = Math.max(res, sum);
    }
    //把计算的结果在存储到map中
    map[left][right] = res;
    return res;
}

在这里插入图片描述

    for (int i = length - 1; i >= 0; i--) {
    
    
        for (int j = i + 2; j <= length + 1; j++) {
    
    
            //计算戳破开区间(i,j)中间的气球所获取的最大硬币数
           ……
        }
    }

如果一个气球被戳破后,本来不挨着的两个气球可能变成挨着的了,这样还是不好计算。我们可以这样来计算,在开区间(i,j)中随便找一个气球,假如是k,我们让k成为最后一个被戳破的气球,那么这样就简单了,当我们戳破气球k的时候所获得的最大硬币是

nums[i]*nums[k]*nums[j];

如下图所示

在这里插入图片描述
这个k可以是开区间(i,j)中的任何一个气球,取最大的即可,所以我们可以找出递推公式,在开区间(i,j)中戳破第k个气球所获得硬币数

sum=temp[i]*temp[k]*temp[j]+dp[i][k]+dp[k][j];

其中dp[i][k]表示戳破开区间(i,k)中的气球所获得的最大硬币数量,同理dp[k][j],来看下代码

public int maxCoins(int[] nums) {
    
    
    int length = nums.length;
    //申请一个比原来长度大2的临时数组
    int[] temp = new int[length + 2];
    //临时数组的首尾值赋为1,中间的值和nums的值一样
    for (int i = 1; i <= length; i++)
        temp[i] = nums[i - 1];
    temp[0] = temp[length + 1] = 1;
    //dp[i][j]表示戳破开区间(i,j)之间的气球所获得的最大硬币数
    int[][] dp = new int[length + 2][length + 2];
    for (int i = length - 1; i >= 0; i--) {
    
    
        for (int j = i + 2; j <= length + 1; j++) {
    
    
            //遍历开区间(i,j)中的每一个气球,尝试戳破他所获得的最大硬币
            for (int k = i + 1; k < j; k++) {
    
    
                //递推公式,戳破气球k可以获得temp[i] * temp[k] * temp[j]个硬币,
                //dp[i][k]表示戳破开区间(i,k)之间的气球所获得的硬币数
                //dp[k][j]表示戳破开区间(k,j)之间的气球所获得的硬币数
                int sum = temp[i] * temp[k] * temp[j] + dp[i][k] + dp[k][j];
                //保留最大的
                dp[i][j] = Math.max(dp[i][j], sum);
            }
        }
    }
    return dp[0][length + 1];
}

猜你喜欢

转载自blog.csdn.net/abcdef314159/article/details/119378101