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. Burp插件开发——环境配置

    最近打算开发个Burp插件,从网上各种地找资料学习.第一步就应该是环境配置,请见下文. (其实最重要的前提是你已经安装了Burp,否则下面的所有内容都是无稽之谈了. https://pan.baidu ...

  2. .Net Core----关于MVC中TempData持久化问题

    最近在做mvc跨控制器传值的时候发现一个问题,就是有时候TempData的值为null,然后查阅了许多资料,发现了许多都是逻辑和原理什么的(想看原理可以查看原理的文章,本文是用法),但是真正解决的办法 ...

  3. 初学ASP.NET 知识点

    1.C# 中的虚方法 和 C++中的作用一致,能让指向子类的父类指针优先到子类中寻找方法,而不是直接调用父类中的方法. 看一段例子回忆下: namespace ConsoleApplication1 ...

  4. symfony composer安装

    参考 http://www.symfonychina.com/doc/current/setup.html 用Composer创建Symfony程序 ¶ 若你已安装过Composer,执行create ...

  5. tensorflow--交叉熵

    学而不思则罔,思而不学则怠. 最近在看<TensorFlow 实战Google深度学习框架第二版>这本书.从头开始学习深度学习,对于细节方面进行探究.相当于重新拾起来这门”手艺“. 这篇随 ...

  6. 使用Open Live Write发布CSDN博客

    ---安装open live write 1.序 在CSDN上发布博客相当麻烦,图片一张张的上传确实让人头大,虽然通过office也能发布博客,不过Open Live Write软件使用感觉更好. 2 ...

  7. jQuery基础操作

    1.jQuery的介绍 jQuery是一个轻量级的.兼容多浏览器的JavaScript库.jQuery使用户能够更方便地处理HTML Document.Events.实现动画效果.方便地进行Ajax交 ...

  8. 防XSS攻击解决方法

    1.web.xml文件中新增filter配置 <!-- URL请求参数字符过滤或合法性校验 --> <filter> <filter-name>XssFilter& ...

  9. Fiddler对Android应用进行抓包

    一:面对安卓包的时候进行分析问题 首先下载fiddler工具进行安装. 使用:第一步:启动Fiddler,打开菜单栏中的 Tools > Fiddler Options,打开“Fiddler O ...

  10. 2018-2019-2 《网络对抗技术》Exp0 Kali安装 Week1

    - 2018-2019-2 <网络对抗技术>Exp0 Kali安装 Week1 - 安装过程 - 安装Kali VMware上学期已经装好了,Kali的镜像文件是从同学那拷过来的,所以这两 ...