尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
C++数字三角形问题与dp算法
前言数字三角形是一道经典的动态规划dynamic programming, DP入门题给定一个由数字组成的三角形从顶部出发每次只能走到下一行相邻的两个位置之一求到达底部时路径上数字之和的最大值。它的规模不大暴力枚举所有路径也能做但路径数量随层数指数增长层数稍多就跑不动。DP 的价值在于把「指数级的路径枚举」压缩成「每层每个位置一个最优值」。最常见的误解是「自顶向下递推时每个位置只保留最大值就够了」。这在求和问题上成立因为加法满足最优子结构到达某位置的最大和等于该位置的值加上到达其上方两个父位置的最大和中的较大者。但如果题目改成「求和的最小值」或者「记录路径」状态定义与转移就要跟着变不能照搬。另一个误解是忽略负数的存在。很多数字三角形的样例全是正数于是有人把「最优值」初始化成 0遇到全负的输入就得到错误答案。本文分别给出自顶向下与自底向上两种写法并说明空间优化与路径还原的处理方式。一、状态定义与转移方程把三角形存成二维数组a[i][j]其中0 j i n。定义dp[i][j]为「从顶部走到第 i 行第 j 列时能获得的最大和」。由于每一步只能从左上(i-1, j-1)或正上(i-1, j)过来转移方程是dp[i][j] a[i][j] max(dp[i-1][j-1], dp[i-1][j])边界要单独处理最左列j 0只能从正上方来最右列j i只能从左上方来顶点dp[0][0] a[0][0]。最终答案是最后一行的最大值而不是dp[n-1][n-1]——终点可以是底部的任意一列。这套定义的时间复杂度是 O(n²)空间复杂度也是 O(n²)。由于每一行的状态只依赖上一行空间可以压缩到 O(n)从右往左更新一维数组避免覆盖还未使用的值。二、自底向上更简洁的写法自底向上的思路是反过来定义状态dp[j]表示「从第 i 行第 j 列出发走到最底部能得到的最大和」。转移变成dp[j] a[i][j] max(dp[j], dp[j1])这里dp[j]是下一行同列的值dp[j1]是下一行右邻列的值。初值是最后一行的元素本身。因为每次只用到下一行一维数组就够而且不需要处理左右边界的特判——j的范围是0..ij1最大取到i1恰好是下一行的有效下标。// 适用于 C17#include algorithm#include cstddef#include iostream#include vectorlong long max_path_sum(const std::vectorstd::vectorint tri) {if (tri.empty()) {return 0;}std::vectorlong long dp(tri.back().begin(), tri.back().end());for (int i static_castint(tri.size()) - 2; i 0; --i) {for (int j 0; j i; j) {dp[static_caststd::size_t(j)] static_castlong long(tri[static_caststd::size_t(i)][static_caststd::size_t(j)]) std::max(dp[static_caststd::size_t(j)],dp[static_caststd::size_t(j) 1]);}}return dp[0];}这段代码有几个点需要核对。i用int并从size() - 2开始是因为size()是std::size_t直接减 2 在空三角或只有一行时会下溢函数开头已经处理了空输入但「只有一行」时size() - 2仍然会回绕所以先转成int再减。dp用long long是因为路径和可能超出int范围而有符号整数溢出是未定义行为undefined behavior, UB不是简单的「变成负数」。tri.back()复制到dp时std::vectorlong long的迭代器范围构造会把每个int转成long long这是合法的隐式转换。若三角形为空tri.back()是 UB所以必须先判断empty()。三、完整示例与路径还原如果只需要最大值上面的函数就够了。如果还需要输出具体路径就得额外记录「每个位置是从左边还是上边来的」。一种做法是再开一个n × n的char表存 0 表示从左上来、1 表示从正上来另一种做法是在自底向上时记录每步选择了哪一侧。// 适用于 C17#include algorithm#include cstddef#include iostream#include vectorint main() {std::vectorstd::vectorint tri {{7},{3, 8},{8, 1, 0},{2, 7, 4, 4},{4, 5, 2, 6, 5}};const int n static_castint(tri.size());// best[i][j]从 (i, j) 到底部的最大和std::vectorstd::vectorlong long best(static_caststd::size_t(n),std::vectorlong long(static_caststd::size_t(n), 0));for (int j 0; j n; j) {best[static_caststd::size_t(n - 1)][static_caststd::size_t(j)] tri[static_caststd::size_t(n - 1)][static_caststd::size_t(j)];}for (int i n - 2; i 0; --i) {for (int j 0; j i; j) {best[static_caststd::size_t(i)][static_caststd::size_t(j)] tri[static_caststd::size_t(i)][static_caststd::size_t(j)] std::max(best[static_caststd::size_t(i 1)][static_caststd::size_t(j)],best[static_caststd::size_t(i 1)][static_caststd::size_t(j) 1]);}}std::cout max sum: best[0][0] \n;int j 0;for (int i 0; i n; i) {std::cout tri[static_caststd::size_t(i)][static_caststd::size_t(j)];if (i 1 n) {const long long left best[static_caststd::size_t(i 1)][static_caststd::size_t(j)];const long long right best[static_caststd::size_t(i 1)][static_caststd::size_t(j) 1];std::cout - ;if (right left) {j;}}}std::cout \n;return 0;}路径还原的关键是站在(i, j)时比较下一行(i1, j)与(i1, j1)的best值谁大就往谁走。若两者相等走哪边都能得到同样的最大和代码里固定走左边right left才右移保证输出确定。打印箭头与选路判断分开写避免把「输出什么」和「走到哪」混在同一个表达式里。用样例手工推演底部是4 5 2 6 5倒数第二行2 7 4 4对应2max(4,5)7、7max(5,2)12、4max(2,6)10、4max(6,5)10继续向上最终顶点值应当是 30路径为7 - 3 - 8 - 7 - 5。这个结果可以逐层核对用来检查实现对不对。四、复杂度与变体写法时间空间特点暴力枚举所有路径O(2ⁿ)O(n) 递归栈只适合极小规模自顶向下 记忆化O(n²)O(n²)思路直观需处理边界自底向上二维O(n²)O(n²)便于还原路径自底向上一维O(n²)O(n)只求值时的最优选择几个常见变体值得注意。若把「最大」改成「最小」转移里的max换成min但初始化不能用 0要用足够大的哨兵值或直接以底行为初值。若允许从底部任意位置出发、到顶部结束把三角形上下翻转即可。若移动规则改成「可以走到下一行任意列」问题就退化成「每行取最大值再求和」不再需要 DP。若数字范围很大路径和要用long long甚至在数值超过 64 位时改用高精度。还有一个容易忽略的点三角形的存储不一定要用vectorvectorint。用一维数组按行优先展开a[i * (i 1) / 2 j]定位缓存局部性更好内存分配次数也更少。对于竞赛类输入这种写法很常见对于可读性优先的业务代码二维结构更清楚。常见坑点坑点一把答案取成最后一行的最后一个元素。❌return dp[n - 1][n - 1];终点可以是底部任意一列。✅ 对最后一行取最大值或在自底向上写法中直接返回dp[0]。坑点二自顶向下时不处理左右边界。❌ 统一写max(dp[i-1][j-1], dp[i-1][j])最左列访问dp[i-1][-1]越界。✅ 最左列只取正上方最右列只取左上方或把数组整体右移一列留出哨兵。坑点三用 0 初始化最优值。❌ 全负数三角形时max结果被 0 拉高得到错误答案。✅ 用底行元素本身作为初值或用负无穷哨兵。坑点四路径和用int累加。❌ 层数多、数值大时溢出属于 UB。✅ 用long long保存dp值输入转换时显式提升。坑点五size() - 2在短输入上下溢。❌for (std::size_t i tri.size() - 2; i 0; --i)空或单行时回绕成巨大值。✅ 先转int并在函数开头处理空输入。坑点六还原路径时只看当前层不看子问题的值。❌ 比较a[i1][j]与a[i1][j1]的大小来决定走向。✅ 比较best[i1][j]与best[i1][j1]即两条子路径的最优值。坑点七认为 DP 一定比暴力快。❌ 在 n 只有 5 时也套一层二维表代码更长且没有实际收益。✅ 理解 DP 的收益来自消除重叠子问题规模极小时暴力反而更简单。总结要点结论状态从某位置出发到底部的最大和转移当前值加下方两个子问题中的较大者方向自底向上无需边界特判空间一维数组滚动O(n)路径还原记录选择或反查子问题的值数值用long long防溢出数字三角形的 DP 之所以经典是因为它把「最优子结构」和「重叠子问题」两件事同时展示了出来每个位置的最优值只由下一层的两个位置决定而这两个位置又被上层大量复用。把状态定义从「从顶部到此处」换成「从此处到底部」边界处理立刻简化这也是解题时值得先想一想的切入点。
RELATED

相关推荐

美赛C题Wordle建模:ARIMA预测、PLS回归与K-means分档

美赛C题Wordle建模:ARIMA预测、PLS回归与K-means分档

简介:这份2023年美赛C题M奖论文以Wordle游戏数据为研究对象,面向美赛参赛者、数据分析学习者及需要毕业设计参考的读者,提供一套从数据预处理到建模评估的完整研究范例。全文围绕时间序列预测、不同尝试次数得分百分比预测、单词词性与难度分…

📅 2026/11/5 13:03:11
多因变量无标签小样本:ARIMA、PLS与K-means建模

多因变量无标签小样本:ARIMA、PLS与K-means建模

简介:面向美赛参赛者与数据分析学习者的2023年美赛C题M论文,聚焦Wordle游戏数据的分析与预测,可作为竞赛论文写作、时间序列与多变量建模的学习参考,也适合毕业设计中的案例借鉴。全文围绕数据清洗与标准化、基于9个单词属性的Spe…

📅 2026/11/5 13:03:11
一物一码防伪溯源系统:扫码验真+红包发放+积分到账全闭环实现

一物一码防伪溯源系统:扫码验真+红包发放+积分到账全闭环实现

简介:本资源是一款面向企业防伪系统开发者与电商技术运营人员的‘一物一码’防伪溯源解决方案,聚焦商品真伪验证、消费者互动激励与全流程追溯三大核心需求,适用于快消、医药、酒类等高仿风险行业。压缩包共153个文件,含37个PHP后…

📅 2026/11/5 13:03:11
MORE NEWS

更多资讯

📰

基于ADS122U04IPWR与STM32F427ZI的工业测温方案设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Dubbo服务下线实时告警:基于ZooKeeper Watch的轻量级监控方案

简介:这是一套面向Java分布式系统运维与开发人员的Dubbo服务高可用监控解决方案,聚焦于Provider异常下线的实时感知与告警响应。资源通过Zookeeper监听机制实现服务状态变更捕获,结合邮件通知模块,在不侵入原有Dubbo代码的前提下&…

📰

SSM超市管理系统源码解析:从分层架构到本地部署与业务修改

简介:这份资源是面向高校计算机专业学生与Java初学者的一套SSM框架课程设计完整项目,围绕超市管理场景,帮助读者完成从框架整合到业务模块落地的实战练习,适合作为毕业设计或课程作业的参考方案。压缩包共792个文件,约…

📰

SSM超市管理系统实战:从课设跑通到二次开发全解析

简介:这份资源是面向高校计算机相关专业学生与Java入门开发者的SSM课程设计参考项目,围绕超市管理场景,解决商品、库存、销售等业务模块从设计到落地的实践需求,适合作为课程作业、毕业设计原型或SSM框架练手案例。压缩包共792个文…

📰

锂电池PACK生产数字孪生平台架构与实现

1. 锂电池PACK生产数字孪生平台建设背景 在新能源行业快速发展的当下,锂电池PACK生产面临着前所未有的质量管控挑战。传统生产模式下,工艺参数分散在各个设备中,质量数据追溯困难,设备之间缺乏有效协同,这些问题直接影…

📰

PIPE4.3.0安装排错:JDK版本、JAVA_HOME与Petri验证

简介:PIPE 4.3.0 是一款基于 Petri 网的图形化建模与仿真软件,面向软件工程、系统分析与教学实验场景,帮助读者完成并发、同步与竞争等系统行为的建模、动态仿真及性质分析。压缩包共 2391 个文件,约 28.43MB,包含大量…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬