There are `N` piles of stones arranged in a row.  The `i`-th pile has `stones[i]` stones.

move consists of merging exactly K consecutive piles into one pile, and the cost of this move is equal to the total number of stones in these K piles.

Find the minimum cost to merge all piles of stones into one pile.  If it is impossible, return -1.

Example 1:

Input: stones = [3,2,4,1], K = 2
Output: 20
Explanation:
We start with [3, 2, 4, 1].
We merge [3, 2] for a cost of 5, and we are left with [5, 4, 1].
We merge [4, 1] for a cost of 5, and we are left with [5, 5].
We merge [5, 5] for a cost of 10, and we are left with [10].
The total cost was 20, and this is the minimum possible.

Example 2:

Input: stones = [3,2,4,1], K = 3
Output: -1
Explanation: After any merge operation, there are 2 piles left, and we can't merge anymore. So the task is impossible.

Example 3:

Input: stones = [3,5,1,2,6], K = 3
Output: 25
Explanation:
We start with [3, 5, 1, 2, 6].
We merge [5, 1, 2] for a cost of 8, and we are left with [3, 8, 6].
We merge [3, 8, 6] for a cost of 17, and we are left with [17].
The total cost was 25, and this is the minimum possible.

Note:

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

这道题给了我们N堆石头,每堆石头有不同的个数,说每次可以合并K堆石头,合并堆的花费就是石头的个数,然后问如何合并,才能使总花费最小。然后给了一些例子,通过观察例子,可以发现,并不是所有的输入都能成功合成一堆,比如例子2,无论先和并哪三堆,最终都会剩下两堆,从而无法进一步合并,因为 K=3,每次至少需要合并三堆。我们当然希望能在开始合并之前就能知道最终是否能成功合并为一堆,而不是算到最后了才发现白忙了一场,所以要来分析一下,什么时候才能最终合并为一堆。再来看看例子2,每次要将三堆合并为一堆,那么就是减少了两堆,而要使得最终能够剩下一堆,其他的都要合并调,假设原来共有n堆,只能剩下一堆,就是说 n-1 堆都要减掉,而每次只能减少 k-1 堆,所以只要 n-1 能够整除 k-1即可,即 (n-1)%(k-1) == 0 成立,这样就可以提前判断了。

好,接下来继续,考虑如何来解题,首先要意识到这道题的情况可能非常多,用暴力搜索的话可能会非常的复杂,而且当前的合并方法完全会影响到之后的合并,所以基本是要放弃 Brute force 的想法的。同样,这道题也不能用贪婪算法,每次都合并石子个数最少的三堆会收敛到局部峰值,不一定是全局的,所以只能另辟蹊径。观察到这题是玩数组的,又是求极值的题目,那么就要祭出神器动态规划 Dynamic Programming 了,先来考虑定义 dp 数组吧,最简单直接的方法肯定直接用个二维的dp数组了,其中 dp[i][j] 表示合并范围 [i, j] 内的石头堆的最小花费,最终 dp[0][n-1] 就是所要求的值。看到了论坛上有人定义了三维的 dp 数组,把每次合并的堆数K也当作一维放入到 dp 数组中了,其实博主觉得不是很有必要,因为像这种必须要对 dp 数组进行升维操作的是当题目中有隐藏信息 Hidden Information,而当前定义的 dp 数组无法重现子问题,即无法找到状态转移方程的时候必须要做的,最典型的例子就是之前那道 Remove Boxes,那道题自区间的 dp 值非常依赖于区间左边相同的数字的个数,而这道题每次合并的堆数K并不是很依赖其他小于K的合并的堆数,所以博主感觉没有必要加。关于含有隐藏信息的 dp 题目,感觉巅峰就属于拣樱桃那题 Cherry Pickup 了吧,现在看还是有点晕,改天还得重新加工一下吧。其实跟这道题最像的当属于打气球那题 Burst Balloons,气球爆了之后,两边的气球就挨到一起了,这里也很类似,石子合并之后,再跟两边的石子堆继续合并,这里的更新方式还是从小区间更新到大区间,跟打气球那题的思路非常的相似,建议先去看看博主的之前那篇博客 Burst Balloons,会对理解这道题大有裨益。

根据之前打气球的经验,要从小区间开始更新,多小呢,从K开始,因为小于K的区间不用更新,其 dp 值一定为0,因为每次必须合并K堆石子,所以区间的长度 len 从K遍历到 n。好,区间长度确定了,现在要确定起点了,i从0遍历到 n-len 即可,有了区间的起点和长度,可以确定区间的终点 j = i+len-1。目标就是要更新区间 [i, j] 的dp值,先初始化为整型最大值。接下来的更新方法,即状态转移方程,就是本题最大的难点了,要求区间 [i, j] 的 dp 值,没法直接得到,但是由于是从小区间开始更新的,所以 suppose 其中的小区间的 dp 值都已经更新好了,就可以将大区间拆成两个小区间来更新了。一般来讲,将一个数组拆成两个非空子数组的时候,会遍历其所有情况,比如 [1, 2, 3, 4],会拆成 [1] 和 [2,3,4],[1,2] 和 [3,4], [1,2,3] 和 [4]。但是这道题由于其特殊性,并不需要遍历所有的拆分情况,因为某些区间是无法通过合并石子堆得到的,就拿上面的例子来说,若 K=3,那么就不需要用 [1,2] 和 [3,4] 来更新整个区间,它们都不到3个,无法合并,所以遍历的时候每次跳过 K-1 个位置即可,用 t 来分别区间 [i, j],然后每次 t += K-1 即可,用两个小区间的 dp 值来更新整个区间。这还没有完,当某个子区间正好可以合并为一堆石子的时候,其 dp 值要加上该区间所有的石子数。举个最简单的例子,比如 [1, 2, 3],K=3,那么我们分割的话,只能用 dp[0][0] + dp[1][2] 来更新 dp[0][2],但是 dp[0][0] 和 dp[1][2] 均为0,因为区间长度均小于3,那么我们的 dp[0][2] 值就无法更新成正确的值了,这三个数字是可以合并的,所以要加上区间内所有的数字之和,而为了快速的求得任意区间和,采用提前建立累加和数组 sums 的方式,来提高计算效率,所以整个状态转移方程为:

dp[i][j] = min(dp[i][j], dp[i][t] + dp[t + 1][j]); -> (i <= t < j)

dp[i][j] += sums[j + 1] - sums[i]; -> if ((j - i) % (K - 1) == 0)

有了状态转移方程,我们就可以写出代码如下:

class Solution {
public:
int mergeStones(vector<int>& stones, int K) {
int n = stones.size();
if ((n - 1) % (K - 1) != 0) return -1;
vector<int> sums(n + 1);
vector<vector<int>> dp(n, vector<int>(n));
for (int i = 1; i < n + 1; ++i) {
sums[i] = sums[i - 1] + stones[i - 1];
}
for (int len = K; len <= n; ++len) {
for (int i = 0; i + len <= n; ++i) {
int j = i + len - 1;
dp[i][j] = INT_MAX;
for (int t = i; t < j; t += K - 1) {
dp[i][j] = min(dp[i][j], dp[i][t] + dp[t + 1][j]);
}
if ((j - i) % (K - 1) == 0) {
dp[i][j] += sums[j + 1] - sums[i];
}
}
}
return dp[0][n - 1];
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/1000

类似题目:

Burst Balloons

Remove Boxes

Cherry Pickup

参考资料:

https://leetcode.com/problems/minimum-cost-to-merge-stones/

https://leetcode.com/problems/minimum-cost-to-merge-stones/discuss/247567/JavaC%2B%2BPython-DP

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Minimum Cost to Merge Stones 混合石子的最小花费的更多相关文章

  1. LeetCode 1000. Minimum Cost to Merge Stones

    原题链接在这里:https://leetcode.com/problems/minimum-cost-to-merge-stones/ 题目: There are N piles of stones ...

  2. [Swift]LeetCode1000. 合并石头的最低成本 | Minimum Cost to Merge Stones

    There are N piles of stones arranged in a row.  The i-th pile has stones[i] stones. A move consists ...

  3. 1000. Minimum Cost to Merge Stones

    There are N piles of stones arranged in a row.  The i-th pile has stones[i] stones. A move consists ...

  4. 动态规划-Minimum Cost to Merge Stones

    2019-07-07 15:48:46 问题描述: 问题求解: 最初看到这个问题的时候第一反应就是这个题目和打破气球的题目很类似. 但是我尝试了使用dp将问题直接转为直接合并到一个堆问题复杂度迅速提高 ...

  5. Leetcode之动态规划(DP)专题-746. 使用最小花费爬楼梯(Min Cost Climbing Stairs)

    Leetcode之动态规划(DP)专题-746. 使用最小花费爬楼梯(Min Cost Climbing Stairs) 数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost ...

  6. Minimum Cost POJ - 2516 (模板题 spfa最小费用最大流)

    题意: 人回家,一步一块钱,有x个人,y个房子,求能回家的最大人数且使之费用最小 解析: 就是....套模板,,,, 建图(⊙﹏⊙)...要仔细观察呐 对于人拆不拆都可以  都能过,,,,这里贴上拆开 ...

  7. Minimum Cost 【POJ - 2516】【网络流最小费用最大流】

    题目链接 题意: 有N个商家它们需要货物源,还有M个货物供应商,N个商家需要K种物品,每种物品都有对应的需求量,M个商家每种物品都是对应的存货,然后再是K个N*M的矩阵表示了K个物品从供货商运送到商家 ...

  8. Leetcode之动态规划(DP)专题-详解983. 最低票价(Minimum Cost For Tickets)

    Leetcode之动态规划(DP)专题-983. 最低票价(Minimum Cost For Tickets) 在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行.在接下来的一年里,你要旅行的 ...

  9. LeetCode 1130. Minimum Cost Tree From Leaf Values

    原题链接在这里:https://leetcode.com/problems/minimum-cost-tree-from-leaf-values/ 题目: Given an array arr of ...

随机推荐

  1. pmi-ACP考试知识点梳理(部分)

    敏捷宣言 个体和互动 高于流程和工具 工作的软件 高于详尽的文档 客户合作 高于合同谈判 响应变化 高于遵循计划 十二条敏捷原则 1 我们最重要的目标,是通过持续不断地及早交付有价值的软件使客户满意. ...

  2. 【easy】746. Min Cost Climbing Stairs 动态规划

    On a staircase, the i-th step has some non-negative cost cost[i]assigned (0 indexed). Once you pay t ...

  3. dijistra

    #include<bits/stdc++.h> using namespace std; ,maxm = ; int begin[maxn],to[maxm],next[maxm],v[m ...

  4. Nginx命令行控制

    在Linux中,需要使用命令行来控制Nginx服务器的启动与停止.重载配置文件.回滚日志文件.平滑升级等行为.默认情况下,Nginx被安装在目录usrlocal/nginx/中,其二进制文件路径为us ...

  5. js原型链+继承 浅析

    名称:    prototype--原型对象    __proto__--属性 原型链与继承网上搜索定义,看起来挺绕的 .先说继承: 所有的对象实例都可以共享原型对象包含的属性和方法  例如一个实例A ...

  6. JavaScript ES6 新特性详解

    JavaScript ES6 带来了新的语法和新的强大功能,使您的代码更现代,更易读 const ,  let and var 的区别: const , let 是 ES6 中用于声明变量的新关键字. ...

  7. selenium数据驱动模式实现163邮箱的登录及添加联系人自动化操作

    项目结构如下: 要求python3.0 selenium3.0 下面是代码: appModubles:addContactPersonActtion.py和LoginAction.py addCont ...

  8. FTP设置

    ftp设置 1.登录服务器 2.程序->关闭或启用windows服务 3.创建用户 4.在iis中增加ftp站点,设置ftpsite,ip和端口号,物理路径 5.设置ftp身份验证 6.访问ft ...

  9. set操作

    Set操作,Set集合就是不允许重复的列表 sadd(name,values) # name对应的集合中添加元素 scard(name) 获取name对应的集合中元素个数 sdiff(keys, *a ...

  10. python输入一行字符,判断不同字符数量

    输入一行字符,判断不同字符的数量, 分别用for循环和while循环完成 for循环 运用了字符串方法, isupper()判断是否为大写字母 islower()判断是否为小写字母 isdigit() ...