探索 C++ 石子合并问题:算法解析与代码实现

在算法学习的漫漫长路上,石子合并问题是一道极具代表性的经典题目,它不仅考验对动态规划算法思想的理解,还能让我们在实践中提升代码编写与问题解决能力。今天,咱们就借助 C++ 这把利器,深入剖析石子合并问题。

一、问题描述

假设有 N 堆石子排成一排,每堆石子有一定数量,记为 a1, a2,..., aN。现要将这些石子合并成一堆,每次只能合并相邻的两堆石子,合并这两堆石子的代价是这两堆石子数量之和。问怎样合并才能使总代价最小?例如,有 3 堆石子分别为 3, 4, 5,若先合并 3 和 4,代价为 3 + 4 = 7,新堆变为 7, 5,再合并代价为 7 + 5 = 12,总代价是 7 + 12 = 19;若先合并 4 和 5,再合并新堆与 3,总代价则是 9 + 12 = 21,可见不同合并顺序代价不同。

二、算法思路:动态规划解法

动态规划关键在于找到最优子结构与重叠子问题。对于石子合并,定义 dp[i][j] 表示将第 i 堆到第 j 堆石子合并成一堆的最小代价。

基础情况:当 i == j 时,只有一堆石子,dp[i][j] = 0,因为无需合并操作。

状态转移方程推导:对于 dp[i][j]i < j),需遍历 i 到 j 间分割点 k,尝试所有合并可能。先合并 i 到 k 堆与 k + 1 到 j 堆,代价是这两部分石子总和 sum[i][j](可预处理求出任意区间石子总和),加上合并这两部分产生的子问题代价 dp[i][k] + dp[k + 1][j],取最小值更新 dp[i][j],即 dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[i][j])i <= k < j)。

三、C++ 代码实现

收起

cpp

#include 
#include 
using namespace std;

const int INF = 0x3f3f3f3f;  // 定义极大值,用于初始化 dp 数组

int stoneMerge(vector& stones) {
    int n = stones.size();
    // 预处理石子总和数组 sum,sum[i][j] 表示第 i 堆到第 j 堆石子总和
    vector> sum(n, vector(n, 0));
    for (int i = 0; i < n; ++i) {
        sum[i][i] = stones[i];
        for (int j = i + 1; j < n; ++j) {
            sum[i][j] = sum[i][j - 1] + stones[j];
        }
    }
    // dp 数组初始化,dp[i][j] 表示合并第 i 堆到第 j 堆石子最小代价
    vector> dp(n, vector(n, INF));
    for (int i = 0; i < n; ++i) dp[i][i] = 0; 

    // 动态规划计算 dp 数组
    for (int len = 2; len <= n; ++len) {  // 枚举合并区间长度
        for (int i = 0; i <= n - len; ++i) {  // 枚举区间起始位置
            int j = i + len - 1;  // 确定区间结束位置
            for (int k = i; k < j; ++k) {
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[i][j]);
            }
        }
    }
    return dp[0][n - 1];  // 返回合并所有石子最小代价
}

你可以这样调用函数:

收起

cpp

int main() {
    vector stones = {3, 4, 5};  // 示例石子堆数据
    cout << "最小合并代价: " << stoneMerge(stones) << endl;
    return 0;
}

这段代码先预处理出石子总和数组 sum,再通过三层循环按区间长度递增顺序计算 dp 数组,最终得到合并所有石子最小代价。外层循环控制合并区间长度从 2 到 n,中层循环确定区间起始位置,内层循环遍历分割点 k 更新 dp[i][j]

四、优化拓展

  1. 空间优化:观察到计算 dp[i][j] 时只用到 dp[i][k] 和 dp[k + 1][j]k >= i 且 k < j),即当前行及上一行数据,可用滚动数组技巧,将二维 dp 降为一维,减少空间复杂度。
  2. 环形石子合并:若石子排成环形,首尾相连,只需复制一份石子序列接在原序列后,长度变为 2n,然后在新序列上求解普通石子合并问题,取 dp[0][n - 1]dp[1][n],...,dp[n - 1][2n - 2] 最小值,因为这些情况涵盖了所有环形合并可能,巧妙转化为已解决问题。

石子合并问题是算法海洋里的一颗璀璨珍珠,吃透它,能帮你更深入理解动态规划精髓,在后续复杂算法挑战中乘风破浪。持续钻研,不断用代码实践,定能提升编程功力!

你可能感兴趣的:(c++,算法,开发语言)