leetcode
leetcode 901 ~ 950
合并石头的最低成本

合并石头的最低成本

难度:

标签:

题目描述

代码结果

运行时间: 32 ms, 内存: 16.8 MB


/*
 * 思路:
 * 使用Java Stream API实现相同的动态规划解决方案。
 * 1. 使用流来初始化前缀和数组。
 * 2. 在DP部分依旧采用嵌套循环实现。
 */

import java.util.Arrays;
import java.util.stream.IntStream;

public class Solution {
    public int mergeStones(int[] stones, int K) {
        int n = stones.length;
        if ((n - 1) % (K - 1) != 0) return -1;
        int[][] dp = new int[n][n];
        int[] prefixSum = new int[n + 1];
        IntStream.range(0, n).forEach(i -> prefixSum[i + 1] = prefixSum[i] + stones[i]);
        IntStream.rangeClosed(K, n).forEach(len ->
            IntStream.rangeClosed(0, n - len).forEach(i -> {
                int j = i + len - 1;
                dp[i][j] = IntStream.iterate(i, m -> m < j, m -> m + K - 1)
                                     .map(m -> dp[i][m] + dp[m + 1][j])
                                     .min().orElse(Integer.MAX_VALUE);
                if ((j - i) % (K - 1) == 0) dp[i][j] += prefixSum[j + 1] - prefixSum[i];
            })
        );
        return dp[0][n - 1];
    }
}

解释

方法:

本题解采用动态规划方法解决合并石头的问题。动态规划数组 f[i][j] 用于存储从 i 到 j 合并成一堆的最小成本。首先检查 (n - k) % (k - 1) 是否为 0,如果不为 0 则返回 -1,因为这意味着不能通过 k 堆合并的方式最终合并成一堆。定义一个前缀和数组 s 以便快速计算任意区间的石头总数。核心的动态规划过程是考虑所有可能的分割方法,即对于每个区间 [i, j],考虑所有可能的中间点 u,通过这些中间点将问题拆解为两个子问题。当区间长度 l 符合可以整体合并的条件时,累加整个区间的石头总量到成本中。

时间复杂度:

O(n^3)

空间复杂度:

O(n^2)

代码细节讲解

🦆
题解中提到的前缀和数组s是如何帮助快速计算任意区间的石头总数的?具体是如何应用在动态规划更新中的?
前缀和数组s的主要作用是快速计算任意子数组的和,以减少重复计算,提高效率。定义s[i]为从数组开始到索引i-1的元素的和。因此,任意区间[i, j]的石头总数可通过s[j+1] - s[i]快速得到。在动态规划中,当要考虑合并整个区间[i, j]时,需要知道这个区间内所有石头的总和来计算合并成本。使用前缀和数组,可以在O(1)时间内得到这个和,从而优化整个动态规划的计算过程。
🦆
在动态规划的过程中,为何在特定的分割点u停止,且步长为k-1?这样的分割是否总是最优?
在动态规划中设置步长为k-1是为了确保每次合并操作后,剩余的堆数仍然能够以k个一组继续合并。这是因为每次合并k个堆成1堆,减少的堆数是k-1。如果不按照k-1的步长分割,则可能导致无法将剩余的堆数继续按k个一组合并。这种分割方式是为了符合题目的合并要求,但并不一定是最优的,因为它主要是按照题目设定的规则来确保合并的可能性,而不是从成本最小化的角度出发。
🦆
对于边界情况,比如数组长度n等于k时,题解中的动态规划方法是否有特殊处理?
当数组长度n等于k时,这是一个特殊情况,因为整个数组可以直接合并成一堆,不需要进一步的分割。在动态规划的初始化过程中,已经将长度为1的区间的合并成本设置为0。对于长度为n的区间,只需要计算一次合并成本,即整个区间的石头总和。因此,这种情况下的处理比较直接,只需在动态规划中考虑将整个区间合并的情况即可。
🦆
题解提到当`(n-k) % (k-1) != 0`时直接返回-1,能否解释为什么n和k的这种关系会导致无法合并成一堆?
如果`(n-k) % (k-1) != 0`,意味着将数组中的n个元素减去一个初始的k个一组的合并后,剩余的元素无法再按照每次减少k-1的规则继续整合为更少的堆数。这是因为每次合并k个堆减少的堆数正好是k-1,如果n-k与k-1的余数不为0,那么在经过若干次合并后,会剩下无法再进行k堆合并的剩余堆数。因此在这种情况下,无法通过规定的合并方式最终合并成一堆,故直接返回-1表示无法完成合并任务。

相关问题

戳气球

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

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

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

 

示例 1:
输入:nums = [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

示例 2:

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

 

提示:

  • n == nums.length
  • 1 <= n <= 300
  • 0 <= nums[i] <= 100

连接木棍的最低费用

连接木棍的最低费用