C++前缀和算法:合并石头的最低成本原理、源码及测试用例(一)

简介: C++前缀和算法:合并石头的最低成本原理、源码及测试用例

本文涉及的基础知识点

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);
}

相关文章
|
3天前
|
算法 Java 数据库
理解CAS算法原理
CAS(Compare and Swap,比较并交换)是一种无锁算法,用于实现多线程环境下的原子操作。它通过比较内存中的值与预期值是否相同来决定是否进行更新。JDK 5引入了基于CAS的乐观锁机制,替代了传统的synchronized独占锁,提升了并发性能。然而,CAS存在ABA问题、循环时间长开销大和只能保证单个共享变量原子性等缺点。为解决这些问题,可以使用版本号机制、合并多个变量或引入pause指令优化CPU执行效率。CAS广泛应用于JDK的原子类中,如AtomicInteger.incrementAndGet(),利用底层Unsafe库实现高效的无锁自增操作。
理解CAS算法原理
|
1月前
|
算法 容器
令牌桶算法原理及实现,图文详解
本文介绍令牌桶算法,一种常用的限流策略,通过恒定速率放入令牌,控制高并发场景下的流量,确保系统稳定运行。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
令牌桶算法原理及实现,图文详解
|
24天前
|
存储 人工智能 缓存
【AI系统】布局转换原理与算法
数据布局转换技术通过优化内存中数据的排布,提升程序执行效率,特别是对于缓存性能的影响显著。本文介绍了数据在内存中的排布方式,包括内存对齐、大小端存储等概念,并详细探讨了张量数据在内存中的排布,如行优先与列优先排布,以及在深度学习中常见的NCHW与NHWC两种数据布局方式。这些布局方式的选择直接影响到程序的性能,尤其是在GPU和CPU上的表现。此外,还讨论了连续与非连续张量的概念及其对性能的影响。
46 3
|
29天前
|
机器学习/深度学习 人工智能 算法
探索人工智能中的强化学习:原理、算法与应用
探索人工智能中的强化学习:原理、算法与应用
|
1月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
72 5
|
28天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
62 1
|
1月前
|
负载均衡 算法 应用服务中间件
5大负载均衡算法及原理,图解易懂!
本文详细介绍负载均衡的5大核心算法:轮询、加权轮询、随机、最少连接和源地址散列,帮助你深入理解分布式架构中的关键技术。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
5大负载均衡算法及原理,图解易懂!
|
1月前
|
缓存 算法 网络协议
OSPF的路由计算算法:原理与应用
OSPF的路由计算算法:原理与应用
50 4
|
1月前
|
存储 算法 网络协议
OSPF的SPF算法介绍:原理、实现与应用
OSPF的SPF算法介绍:原理、实现与应用
87 3
|
29天前
|
机器学习/深度学习 人工智能 算法
探索人工智能中的强化学习:原理、算法及应用
探索人工智能中的强化学习:原理、算法及应用