news 2026/4/16 14:31:38

圆形石子合并问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
圆形石子合并问题

在算法设计中,圆形石子合并问题是经典的动态规划应用场景之一。本文将详细讲解该问题的解法,并用 C++ 实现 3 种不同算法,最后对比它们的优劣。

一、问题描述

在圆形操场周围有n堆石子,每次只能合并相邻的 2 堆,合并得分是新堆的石子数。求将所有石子合并为 1 堆的最小得分最大得分

二、算法 1:基础动态规划(环形转线性)

思路

圆形结构可通过复制数组转化为线性结构(长度为2n),再用区间 DP 求解:

  • 定义dp_min[i][j]:合并区间[i,j]的最小得分
  • 定义dp_max[i][j]:合并区间[i,j]的最大得分
  • 状态转移:dp_min[i][j]=min(dp_min[i][k]+dp_min[k+1][j])+sum(i,j)(i≤k<j)dp_max[i][j]=max(dp_max[i][k]+dp_max[k+1][j])+sum(i,j)(i≤k<j)
  • sum(i,j)是区间[i,j]的石子总数(用前缀和快速计算)

C++ 代码

#include <iostream> #include <vector> #include <climits> using namespace std; // 计算前缀和 vector<int> getPrefixSum(const vector<int>& arr) { vector<int> pre(arr.size() + 1, 0); for (int i = 0; i < arr.size(); ++i) { pre[i+1] = pre[i] + arr[i]; } return pre; } // 区间[i,j]的和(基于前缀和) int getSum(const vector<int>& pre, int i, int j) { return pre[j+1] - pre[i]; } pair<int, int> stoneMergeDP(vector<int> stones) { int n = stones.size(); if (n == 1) return {0, 0}; // 环形转线性:复制数组 vector<int> arr = stones; arr.insert(arr.end(), stones.begin(), stones.end()); vector<int> pre = getPrefixSum(arr); int len = 2 * n; vector<vector<int>> dp_min(len, vector<int>(len, 0)); vector<vector<int>> dp_max(len, vector<int>(len, 0)); // 枚举区间长度(从2到n) for (int l = 2; l <= n; ++l) { for (int i = 0; i + l <= len; ++i) { int j = i + l - 1; dp_min[i][j] = INT_MAX; dp_max[i][j] = INT_MIN; // 枚举分割点 for (int k = i; k < j; ++k) { int sum = getSum(pre, i, j); dp_min[i][j] = min(dp_min[i][j], dp_min[i][k] + dp_min[k+1][j] + sum); dp_max[i][j] = max(dp_max[i][j], dp_max[i][k] + dp_max[k+1][j] + sum); } } } // 遍历所有长度为n的区间,取最小/最大值 int min_res = INT_MAX, max_res = INT_MIN; for (int i = 0; i < n; ++i) { min_res = min(min_res, dp_min[i][i + n - 1]); max_res = max(max_res, dp_max[i][i + n - 1]); } return {min_res, max_res}; } int main() { vector<int> stones = {4, 1, 2, 3}; // 测试用例 auto [min_score, max_score] = stoneMergeDP(stones); cout << "最小得分:" << min_score << endl; cout << "最大得分:" << max_score << endl; return 0; }

三、算法 2:贪心算法(仅适用于链形 + 特殊条件)

思路

贪心算法仅在链形结构 + 石子数非递减 / 非递增时有效(圆形结构不适用),核心是:

  • 最小得分:每次合并最小的相邻两堆(类似哈夫曼编码)
  • 最大得分:每次合并最大的相邻两堆

C++ 代码(链形场景)

#include <iostream> #include <vector> #include <queue> using namespace std; // 贪心求最小得分(链形) int greedyMin(vector<int> stones) { priority_queue<int, vector<int>, greater<int>> pq(stones.begin(), stones.end()); int res = 0; while (pq.size() > 1) { int a = pq.top(); pq.pop(); int b = pq.top(); pq.pop(); res += a + b; pq.push(a + b); } return res; } // 贪心求最大得分(链形) int greedyMax(vector<int> stones) { priority_queue<int> pq(stones.begin(), stones.end()); int res = 0; while (pq.size() > 1) { int a = pq.top(); pq.pop(); int b = pq.top(); pq.pop(); res += a + b; pq.push(a + b); } return res; } int main() { vector<int> stones = {4, 1, 2, 3}; // 链形测试用例 cout << "链形最小得分(贪心):" << greedyMin(stones) << endl; cout << "链形最大得分(贪心):" << greedyMax(stones) << endl; return 0; }

四、算法 3:记忆化搜索(递归 + 缓存)

思路

基于基础 DP 的递归实现,用记忆化缓存避免重复计算,逻辑与基础 DP 一致,但代码更简洁。

C++ 代码

#include <iostream> #include <vector> #include <climits> #include <unordered_map> using namespace std; vector<int> pre; vector<vector<int>> memo_min, memo_max; int n; int dfs_min(int i, int j) { if (i == j) return 0; if (memo_min[i][j] != -1) return memo_min[i][j]; int res = INT_MAX; for (int k = i; k < j; ++k) { int sum = pre[j+1] - pre[i]; res = min(res, dfs_min(i, k) + dfs_min(k+1, j) + sum); } return memo_min[i][j] = res; } int dfs_max(int i, int j) { if (i == j) return 0; if (memo_max[i][j] != -1) return memo_max[i][j]; int res = INT_MIN; for (int k = i; k < j; ++k) { int sum = pre[j+1] - pre[i]; res = max(res, dfs_max(i, k) + dfs_max(k+1, j) + sum); } return memo_max[i][j] = res; } pair<int, int> stoneMergeMemo(vector<int> stones) { n = stones.size(); if (n == 1) return {0, 0}; vector<int> arr = stones; arr.insert(arr.end(), stones.begin(), stones.end()); pre.resize(arr.size() + 1, 0); for (int i = 0; i < arr.size(); ++i) { pre[i+1] = pre[i] + arr[i]; } memo_min.assign(2*n, vector<int>(2*n, -1)); memo_max.assign(2*n, vector<int>(2*n, -1)); int min_res = INT_MAX, max_res = INT_MIN; for (int i = 0; i < n; ++i) { min_res = min(min_res, dfs_min(i, i + n - 1)); max_res = max(max_res, dfs_max(i, i + n - 1)); } return {min_res, max_res}; } int main() { vector<int> stones = {4, 1, 2, 3}; auto [min_score, max_score] = stoneMergeMemo(stones); cout << "最小得分(记忆化):" << min_score << endl; cout << "最大得分(记忆化):" << max_score << endl; return 0; }

五、算法优劣对比

算法时间复杂度空间复杂度适用场景优点缺点
基础 DPO(n3)O(n2)圆形 / 链形通用、结果准确时间复杂度高
贪心算法O(nlogn)O(n)链形 + 特殊石子序列效率高圆形场景不适用、结果可能错误
记忆化搜索O(n3)O(n2)圆形 / 链形代码简洁、逻辑直观递归栈深度限制、效率略低于迭代 DP

六、总结

圆形石子合并问题的最优解法是基础动态规划(或记忆化搜索),贪心算法仅适用于特殊场景。实际应用中,若n较大(如n>100),需优化 DP(如四边形不等式优化,时间复杂度可降为 O(n2))。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:28:21

12.20问题盘点

在匹配条件的时候完全就想少了,副对角线的特征就是行数加列数为总行数最开始写的循环条件是不等于\0,结果运行超时&#xff0c;很明显就是忘记了\0不是用户输入的&#xff0c;是电脑自带的键盘上是不存在这个输入的&#xff0c;就会导致根本输入就结束不了&#xff0c;输出就会…

作者头像 李华
网站建设 2026/4/12 14:36:17

2025年大模型使用全景图:6大趋势助你抢占AI先机

本文基于2025年全球LLM使用年报&#xff0c;揭示六大趋势&#xff1a;市场将形成开源与闭源双生态&#xff1b;代理式推理取代对话成为主流&#xff1b;编程跃升为第一大使用场景&#xff1b;用户更看重模型能力而非价格&#xff1b;模型需满足特定需求保持用户粘性&#xff1b…

作者头像 李华
网站建设 2026/4/16 10:38:00

电路板维修

稳压二极管击穿、电容击穿&#xff0c;正极对负极直接短路 &#xff0c;把电源直接拉到0查找短路源&#xff1a;熏松香&#xff0c;哪个芯片短路&#xff0c;供电后会发热&#xff0c;上面的白霜就会融化检查开发板是否短路用DC电源给开发板供电&#xff0c;看电流是否在正常范…

作者头像 李华
网站建设 2026/4/15 15:01:10

一文搞懂DNAT与SNAT:内网外网通信的“流量翻译官”

导读:在运维工作中,内网服务器访问外网、外网用户访问内网服务,是最常见的场景之一。而实现这两种通信的核心技术,就是DNAT和SNAT。很多运维同学刚接触时,总会混淆两者的作用——到底哪个是“内网出外网”用的?哪个是“外网进内网”用的?配置时又该注意什么?本文就从实…

作者头像 李华
网站建设 2026/4/16 12:18:24

智能建议模块 Cordova 与 OpenHarmony 混合开发实战

欢迎大家加入开源鸿蒙跨平台开发者社区&#xff0c;一起共建开源鸿蒙跨平台生态。 &#x1f4cc; 概述 智能建议模块是福报养成计应用中的一个关键功能&#xff0c;它基于用户的历史数据和行为模式&#xff0c;使用机器学习和数据分析算法来识别用户的福报积累规律&#xff0c…

作者头像 李华
网站建设 2026/4/16 12:22:57

160. 相交链表

160. 相交链表 - 力扣&#xff08;LeetCode&#xff09; 简单 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保…

作者头像 李华