本文涉及的基础知识点
C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
动态规划,日后完成。
题目
有 n 堆石头排成一排,第 i 堆中有 stones[i] 块石头。
每次 移动 需要将 连续的 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,这是可能的最小值。
提示:
n == stones.length
1 <= n <= 30
1 <= stones[i] <= 100
2 <= k <= 30
分析
dp[begin][end]记录stones[begin,end)合并后的最小得分。时间复杂度O(nnn),状态数:n*n,转移状态时间复杂度O(n)。
状态转移
假定stones[begin,end)是由stone[begin,m)和stone[m,end)合并成的,m取值范围(begin,end)。stone[begin,m)简称左堆,stone[m,end)简称右堆。
左右两堆剩余石头数之和小于k | dp[begin][end] = dp[begin][m]+dp[m][end] |
左右两堆剩余石头数之和等于于k | dp[begin][end] = dp[begin][m]+dp[m][end]+vPreSum[begin][end],石头发生了合并 |
左右两堆剩余石头数之和大于于k | 抛弃 |
左右两堆剩余石头数之和大于于k
抛弃左右两堆剩余石头数之和大于于k,也可以找到最优解。
最后一轮 | 只有k个石头,故不会超过k |
倒数第二轮 | 只有2k-1个石头,假定其范围是[i0,j0),倒数第二轮是[i1,j1), 那么[i0,j0)会合并,这时两堆石头恰好是k,故不会超过k |
… | … |
剩余石头数
每次合并后,石头数减少k-1。所有石头数减1,再对k-1求求余,再加1。
注意:先判断石头数是否是1,不是直接返回-1。
代码
核心代码
class Solution { public: int mergeStones(vector<int>& stones, int K) { m_c = stones.size(); if (1 != RemainLen(m_c,K)) { return -1; } vector<int> vPreSum = { 0 }; for (const auto& n : stones) { vPreSum.emplace_back(n + vPreSum.back()); } vector<vector<int>> dp(m_c,vector<int>(m_c+1));//dp[i][j] 表示合并stones[i,j)的最小成本 for (int len = 2; len <= m_c; len++) { for (int begin = 0; begin + len <= m_c; begin++) { const int end = begin + len; int iMin = INT_MAX; for (int m = begin + 1; m < end; m++) { const int iAdd = RemainLen(m - begin, K) + RemainLen(end - m, K); if (iAdd > K) { continue; } int cur = dp[begin][m] + dp[m][end]; iMin = min(iMin, cur); } if (1 == RemainLen(len, K)) { iMin += vPreSum[end] - vPreSum[begin]; } dp[begin][end] = iMin; } } return dp.front().back(); } int RemainLen(int len, int k) { return 1+(len - 1) % (k - 1); } int m_c; };
测试代码
template<class T> void Assert(const vector<T>& v1, const vector<T>& v2) { if (v1.size() != v2.size()) { assert(false); return; } for (int i = 0; i < v1.size(); i++) { assert(v1[i] == v2[i]); } } template<class T> void Assert(const T& t1, const T& t2) { assert(t1 == t2); } int main() { vector<int> stones = { 3,5,1,2,6 }; int k = 3; int res = Solution().mergeStones(stones, k); Assert(25, res); stones = { 3,2,4,1 }; k = 2; res = Solution().mergeStones(stones, k); Assert(20, res); stones = { 1,2,3,4,5,6,7 }; k = 3; res = Solution().mergeStones(stones, k); Assert(49, res); stones = { 1,2,3,4,5,6,7 }; k = 4; res = Solution().mergeStones(stones, k); Assert(38, res); stones = { 1,2,3,4,5,6,7,8,9 }; k = 5; res = Solution().mergeStones(stones, k); Assert(60, res); // stones = { 9, 8, 7, 6, 5, 4, 3, 2, 1 }; k = 2; res = Solution().mergeStones(stones, k); Assert(135, res); stones = { 9,8,7,6,5,4,3,2,1 }; k = 3; res = Solution().mergeStones(stones, k); Assert(87, res); stones = { 10,9,8,7,6,5,4,3,2,1 }; k = 4; res = Solution().mergeStones(stones, k); Assert(91, res); // stones = { 5,8,7,6,5,12,13,14,4,3,2,1,2 }; k = 4; res = Solution().mergeStones(stones, k); Assert(155, res); stones = { 2,8,7,6,5,12,13,14,4,3,2,1,2 }; k = 5; res = Solution().mergeStones(stones, k); Assert(119, res); //CConsole::Out(res); }