• 【LeetCode】合并石头的最低成本 [H](动态规划)


    1000. 合并石头的最低成本 - 力扣(LeetCode)

    一、题目

    有 N 堆石头排成一排,第 i 堆中有 stones[i] 块石头。

    每次移动(move)需要将连续的 K 堆石头合并为一堆,而这个移动的成本为这 K 堆石头的总数。

    找出把所有石头合并成一堆的最低成本。如果不可能,返回 -1 。

    示例 1:
    输入:stones = [3,2,4,1], K = 2
    输出:20
    解释:
    从 [3, 2, 4, 1] 开始。
    合并 [3, 2],成本为 5,剩下 [5, 4, 1]。
    合并 [4, 1],成本为 5,剩下 [5, 5]。
    合并 [5, 5],成本为 10,剩下 [10]。
    总成本 20,这是可能的最小值。

    示例 2:
    输入:stones = [3,2,4,1], K = 3
    输出:-1
    解释:任何合并操作后,都会剩下 2 堆,我们无法再进行合并。所以这项任务是不可能完成的。

    示例 3:
    输入:stones = [3,5,1,2,6], K = 3
    输出:25
    解释:
    从 [3, 5, 1, 2, 6] 开始。
    合并 [5, 1, 2],成本为 8,剩下 [3, 8, 6]。
    合并 [3, 8, 6],成本为 17,剩下 [17]。
    总成本 25,这是可能的最小值。

    提示:

    • 1 <= stones.length <= 30
    • 2 <= K <= 30
    • 1 <= stones[i] <= 100

    二、代码

    1. class Solution {
    2. public int mergeStones(int[] stones, int k) {
    3. // 过滤无效参数
    4. if (stones == null || stones.length == 0 || k == 0) {
    5. return -1;
    6. }
    7. // 数组长度
    8. int n = stones.length;
    9. // 这个条件很重要,存在一些数组长度n和k的组合压根就合并不成1个,直接返回-1
    10. if ((n - 1) % (k - 1) > 0) {
    11. return -1;
    12. }
    13. // 构造前缀和数组,加速求任意区间累加和的速度
    14. int[] preSum = new int[n];
    15. preSum[0] = stones[0];
    16. for (int i = 1; i < n; i++) {
    17. preSum[i] = preSum[i - 1] + stones[i];
    18. }
    19. // 构造三维dp缓存 第三维下标在递归中最多赋值就是k,所以长度开到k+1即可
    20. int[][][] dp = new int[n][n][k + 1];
    21. // 将数组0~n-1范围合并成1份,每一次将相邻的k个合并
    22. return process(stones, 0, n - 1, 1, k, preSum, dp);
    23. }
    24. // arr[l..r] 一定要弄出p份,返回最低代价
    25. // 固定参数是k和preSum,preSum是用来加快求累加和速度的
    26. public int process(int[] stones, int l, int r, int p, int k, int[] preSum, int[][][] dp) {
    27. // 先看是否有缓存,有的话直接返回
    28. if (dp[l][r][p] != 0) {
    29. return dp[l][r][p];
    30. }
    31. // basecase 如果此时l==r,说明本身就是1个数了,如果要划分的目标p也是1,那么就不需要再合并了,代价直接返回0,如果目标p不是1,但是因为此时l~r范围只有一个数了,肯定是合并不出来p目标了,直接返回-1
    32. if (l == r) {
    33. dp[l][r][p] = p == 1 ? 0 : -1;
    34. return dp[l][r][p];
    35. }
    36. // 说明当前就是最后一次合并了,合并完就要求是1个,如果无法一次合并成1个,就返回-1
    37. if (p == 1) {
    38. // 当前是最后一次合并了,如果要想是最后一次合并,此时合并完只能由k个数,所以要判断当前的状况能不能合并出k份
    39. int next = process(stones, l, r, k, k, preSum, dp);
    40. // 如果不能合并成k份,那么一定就无法最后合并成一份,直接返回-1
    41. if (next == -1) {
    42. dp[l][r][p] = -1;
    43. return dp[l][r][p];
    44. // 可以合并出三份
    45. } else {
    46. // 代价就是合并出三分所需要的总代价,加上l~r范围的累加和,因为将3份数合并,合并代价就是这三部分的累加和
    47. dp[l][r][p] = next + (l == 0 ? preSum[r] : preSum[r] - preSum[l - 1]);
    48. return dp[l][r][p];
    49. }
    50. // 当前目标并不是分成1份,说明合并还没有到最后
    51. } else {
    52. // 先将最小代价初始化为系统最大
    53. int min = Integer.MAX_VALUE;
    54. // 开始遍历,尝试所有可能的左右部分划分情况,我们就规定左部分划分出1份,右部分划分出p-1份
    55. // 划分遍历步长就是k - 1,l最多到r - (p - 1),因为要保证右部分至少有p-1个数,不然就不可能划分出p-1个目标了
    56. for (int mid = l; mid <= r - (p - 1); mid += (k - 1)) {
    57. // 看左部分合并成1份最低代价
    58. int leftCost = process(stones, l, mid, 1, k, preSum, dp);
    59. int rightCost = -1;
    60. // 如果左部分确实可以划分出1份,就去看右部分能不能划分出p-1份。剪枝
    61. if (leftCost != -1) {
    62. // 右部分合并出p-1份的最低代价
    63. rightCost = process(stones, mid + 1, r, p - 1, k, preSum, dp);
    64. }
    65. // 如果右部分也可以划分出来,则计算这两部分的合并代价,是否是l~r范围所有划分情况中最低的
    66. if (rightCost != -1) {
    67. // 这里我们就是要将l~r范围合并出p份,并不会对这p份再做合并,所以这里的代价就是leftCost+rightCost,不需要加上l~r的累加和
    68. min = Math.min(min, leftCost + rightCost);
    69. }
    70. }
    71. // 返回所有划分中合并代价最小的值
    72. dp[l][r][p] = min;
    73. return dp[l][r][p];
    74. }
    75. }
    76. }

    三、解题思路 

    定义process函数:让L...R一定变为P份,返回合并成P份的最少代价是啥。

    枚举最左边的一份是什么范围,这样就可以递归出左部分和右部分的最低代价。我们将每一层递归的所有可能的范围都枚举尝试一遍,最后就能求出来结果。

    这里我们就认为左边搞成1份,右边搞成P-1份,因为总归是存在一种划分能让左边搞出来1份,我们把所有可能的左部分范围都尝试一遍,就能得到全部左部分和右部分的情况。而且必须要来一个左右部分,因为只有划分为1份的时候才是递归出口,所以我们就要想到让左边固定搞1份,这样才能让整个递归过程结束返回。

  • 相关阅读:
    【LeetCode】每日一题 2023_11_18 数位和相等数对的最大和(模拟/哈希)
    数据库系统概论(超详解!!!) 第三节 关系数据库标准语言SQL(Ⅳ)
    2022-08-05 粗糙集Rough set
    paddlepaddle模型的保存和加载
    MQTT协议详解及在Android上的应用
    k8s--基础--22.11--storageclass--类型--Azure 文件
    MobTech 秒验常见问题
    从零到一落地接口自动化测试
    2001. 可互换矩形的组数-快速排序
    为什么几乎所有的量化交易都用Python?
  • 原文地址:https://blog.csdn.net/cy973071263/article/details/128123782