
Project Euler Problem 67 深度解析cosmos 仓库中 100 层三角形最大路径和的动态规划实现【免费下载链接】cosmosWorlds largest Contributor driven code dataset | Used in Quark Search Engine, OpenGenus IQ, OpenGenus Visual Project项目地址: https://gitcode.com/gh_mirrors/co/cosmosProject Euler 第 67 题Maximum path sum II是经典三角形最大路径和问题的进阶版本给定一个 100 行的数字三角形从顶部出发每一步只能移动到下一行相邻的数字求从顶到底能得到的最大路径和。它在当前仓库中由 problem_067/README.md 给出题目描述并由 problem_067.py 提供完整 Python 实现。读完本文你将掌握该问题从暴力枚举到自底向上动态规划的完整推导、源码逐行剖析、复杂度分析以及可验证的最终答案并能把同一套方法迁移到任意规模的数字三角形问题上。问题描述与原题示例原题以一个小三角形解释了移动规则从顶部数字出发每一步只能走向下一行相邻的数字即正下方或右下方累计经过的所有数字之和即为一条路径的得分目标是找到得分最大的那条路径。对于下面这个 4 行的小三角形3 7 4 2 4 6 8 5 9 3最大路径为3 → 7 → 4 → 9路径和为3 7 4 9 23。注意这里相邻的严格含义位于第 i 行第 j 列的数字只能走到第 i1 行的第 j 列或第 j1 列。真正需要求解的目标是在官方提供的一个100 行、约 15KB 的文本文件triangle.txt原文档提示可通过右键另存为下载中计算从顶到底的最大路径和。这个规模正是与第 18 题的本质区别所在。为什么 100 行的规模不能暴力求解problem_018Maximum path sum I使用的是一个 15 行的三角形其题目描述中明确给出了一个关键注记由于只有 16384 条路径可以通过尝试每一条路径来求解但第 67 题是同一个挑战只是三角形包含 100 行它无法通过暴力求解需要更巧妙的方法。16384 恰是2^14——对于 n 行三角形从顶到底的路径总数为2^(n-1)。原因是每向下走一层都要在左下/右下两个选项中二选一共需选择 n-1 次。当 n15 时约 1.6 万条路径暴力可行当 n100 时路径数爆炸为2^99约 6.3 × 10^29 条即使每秒枚举 10 亿条路径也需要远超宇宙年龄的时间。这正是该题要求巧妙方法的根本原因。核心算法自底向上的动态规划解决这个问题的标准方法是动态规划它建立在两个关键性质之上最优子结构从三角形某一行某个数字出发的最优路径和只取决于该数字本身以及其两个子节点左下方、右下方各自的最优子路径和。重叠子问题不同路径会反复经过相同的数字节点若递归枚举会产生大量重复计算用 DP 表把每个节点处的最优解记录下来即可避免重复。更具体地定义dp[i][j]为从第 i 行第 j 列的数字出发走到三角形底部能获得的最大路径和则转移方程为dp[i][j] triangle[i][j] max(dp[i1][j], dp[i1][j1])边界是最后一行dp[n-1][j] triangle[n-1][j]即最后一行任何数字走到自己就是终点。最终答案就是dp[0][0]。实现上有两种遍历方向自顶向下用推的方式传播前缀和自底向上用拉的方式归并子问题。仓库源码采用自底向上并且做了一个非常优雅的优化——直接在原数组上原地更新不需要额外申请 DP 表从而把额外空间压到 O(1)。仓库源码逐行剖析problem_067.py 全文件约 5030 行其中绝大部分前 5023 行是把官方triangle.txt的 100 行数据直接内嵌为 Python 嵌套列表prob结构如下节选开头几行def main(): prob [ [59], [73, 41], [52, 40, 9], [26, 53, 6, 34], ... ]从源码结构可以看到prob[i]表示第 i 行i 从 0 开始行内恰好有 i1 个数字prob[0]是三角形顶端数字 59prob[99]是 100 个数字构成的最后一行。把数据直接内嵌进源码使得该文件无需依赖外部文件即可独立运行这是单文件可执行的解题风格的体现。真正的算法核心只有三行位于文件末尾problem_067.pyfor i in range(98, -1, -1): for j in range(len(prob[i])): prob[i][j] max(prob[i 1][j], prob[i 1][j 1]) print(prob[0][0])逐一拆解这段代码外层循环for i in range(98, -1, -1)从倒数第二行索引 98向上遍历到第 0 行。因为第 99 行是边界无需更新所以从 98 开始-1步长保证倒序确保计算第 i 行时第 i1 行已经是归并完成的最优值。内层循环for j in range(len(prob[i]))遍历当前行的每一个数字。状态转移prob[i][j] max(prob[i 1][j], prob[i 1][j 1])把当前数字加上其两个子节点正下方prob[i1][j]与右下方prob[i1][j1]中较大的那个最优子路径和更新后的prob[i][j]即成为从该节点到底部的最大路径和。这与前面的 DP 转移方程完全一一对应。输出答案print(prob[0][0])所有行归并完毕后顶端数字携带的就是全局最大路径和。注意这里的原地更新是安全的计算第 i 行时只读取第 i1 行的值而第 i1 行在上一轮外层迭代中已被更新为最优值之后不会再被修改因此不存在数据被覆盖的冲突。这段实现与姊妹题 problem_018.py 的算法骨架完全一致后者只把外层循环改为range(13, -1, -1)以适配 15 行三角形两处可以相互对照学习——同一个 DP 核心只需按行数微调即可解决两个问题。复杂度分析与答案验证时间复杂度内层循环对每个三角形节点恰好执行一次常数时间的max与加法操作。n 行三角形共有n(n1)/2个节点当 n100 时约为 5050 次操作即 O(n²)。对比暴力枚举的 O(2^n)这是从指数级到多项式级的根本性跨越。空间复杂度算法直接复用输入数组prob没有申请任何额外 DP 表辅助空间为 O(1)不含存储输入本身所占的空间。运行验证在仓库根目录执行python3 code/online_challenges/src/project_euler/problem_067/problem_067.py程序秒级输出7273即 100 行三角形从顶到底的最大路径和为7273该结果与 Project Euler 官方答案一致可作为自测判据。工程化扩展从文件读取与路径重构内嵌数据的写法适合一次提交、独立运行的在线判题场景但在实际工程中数据与算法解耦更常见。可以把官方triangle.txt放在任意路径如本仓库 code/online_challenges/src/project_euler 下的某个数据文件用如下方式读取并求解def load_triangle(path): triangle [] with open(path) as f: for line in f: triangle.append([int(x) for x in line.split()]) return triangle def max_path_sum(triangle): dp [row[:] for row in triangle] # 复制避免修改原始数据 for i in range(len(dp) - 2, -1, -1): for j in range(len(dp[i])): dp[i][j] max(dp[i 1][j], dp[i 1][j 1]) return dp[0][0]如果还想输出具体的路径而不只是最大和可以额外维护一个选择方向的表在每步转移时记录当前节点选择了左子还是右子最后从顶端沿记录回溯即可重构整条路径。这是 DP 题中求最优值 还原最优方案的通用套路。小结Project Euler 67 的核心价值在于用一道看似只有几行代码的题目深刻演示了动态规划最本质的思维转变当暴力枚举随规模指数爆炸时利用最优子结构与重叠子问题把问题化简为逐层归并的 O(n²) 多项式算法。仓库中的 problem_067.py 用三行核心代码 内嵌数据给出了一个干净、可复现、答案可验证7273的参考实现而它与 problem_018 的同构关系也再次印证了同一算法思想可平滑扩展至更大规模这一 DP 方法论。更多 Project Euler 多语言题解可查阅 project_euler 目录。【免费下载链接】cosmosWorlds largest Contributor driven code dataset | Used in Quark Search Engine, OpenGenus IQ, OpenGenus Visual Project项目地址: https://gitcode.com/gh_mirrors/co/cosmos创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考