LeetCode 120. 三角形最小路径和:动态规划详解

在LeetCode的动态规划题目中,「三角形最小路径和」是一道经典的入门级题目,它既考察了对动态规划核心思想的理解,也需要我们结合题目特性设计合理的状态转移方程。今天就来一步步拆解这道题,从题目分析到代码实现,再到思路优化,帮你彻底搞懂这道题的解题逻辑。

一、题目回顾:明确需求与约束

题目给出一个三角形 triangle,要求找出自顶向下的最小路径和,核心约束如下:

  • 每一步只能移动到下一行中「相邻的结点」;

  • 相邻结点定义:当前行下标为 i,下一步可移动到下一行的 ii+1 下标;

  • 三角形的行数不固定,每一行的元素个数等于当前行的行号(从0开始计数的话,第0行1个元素,第1行2个元素,以此类推)。

举个简单示例:若三角形为 [[2],[3,4],[6,5,7],[4,1,8,3]],自顶向下的最小路径和为 2→3→5→1 = 11。

二、解题思路:为什么用动态规划?

这道题的核心是「求最小路径和」,而路径的选择具有「重叠子问题」和「最优子结构」,这正是动态规划的适用场景:

  1. 重叠子问题:到达第 i 行第 j 个元素的最小路径和,依赖于到达第 i-1 行第 j 个和第 j-1 个元素的最小路径和(除了边界情况);

  2. 最优子结构:到达第 i 行第 j 个元素的最小路径和,是其上方两个可能路径的最小路径和加上当前元素的值,即「局部最优推导全局最优」。

因此,我们可以用动态规划的思路,通过构建一个DP数组,逐步计算每一步的最小路径和,最终得到整个三角形的最小路径和。

三、代码逐行解析:读懂每一步的意义

先给出完整的解题代码(题目要求的TypeScript版本),再逐行拆解其逻辑:

typescript 复制代码
function minimumTotal(triangle: number[][]): number {
  const Len = triangle.length;
  const dp: number[][] = new Array(Len);
  for (let i = 0; i < Len; i++) {
    dp[i] = new Array(i + 1).fill(0);
  }
  dp[0][0] = triangle[0][0];
  for (let i = 1; i < Len; i++) {
    for (let j = 0; j <= i; j++) {
      if (j === 0) {
        dp[i][j] = dp[i - 1][j] + triangle[i][j];
      } else if (j === i) {
        dp[i][j] = dp[i - 1][j - 1] + triangle[i][j];
      } else {
        dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle[i][j];
      }
    }
  }
  return Math.min(...dp[Len - 1]);
};

1. 初始化DP数组

首先获取三角形的行数 Len,然后创建一个二维DP数组 dp,其中 dp[i][j] 表示「到达第 i 行第 j 个元素的最小路径和」。

由于第 i 行有 i+1 个元素(行号从0开始),所以我们通过循环给每一行的DP数组初始化,长度为 i+1,初始值填充为0。

2. 边界条件:三角形顶部

三角形的顶部只有一个元素(第0行第0列),到达这个元素的路径只有一条,所以 dp[0][0] = triangle[0][0],这是整个DP数组的起始值。

3. 状态转移:填充DP数组

从第1行开始(因为第0行已经初始化),逐行逐列填充DP数组,分三种情况讨论:

  • j === 0(当前列是当前行的第一列):只能从上层的第一列(j=0)移动过来,所以 dp[i][j] = dp[i-1][j] + triangle[i][j]

  • j === i(当前列是当前行的最后一列):只能从上层的最后一列(j-1)移动过来,所以 dp[i][j] = dp[i-1][j-1] + triangle[i][j]

  • 其他情况(中间列):可以从上层的 j 列或 j-1 列移动过来,取两者的最小值,再加上当前元素的值,即 dp[i][j] = Math.min(dp[i-1][j], dp[i-1][j-1]) + triangle[i][j]

4. 最终结果:取最后一行的最小值

DP数组填充完成后,最后一行的每个元素 dp[Len-1][j],分别表示到达三角形底部每一个元素的最小路径和。我们只需要取这一行的最小值,就是整个三角形的最小路径和,用 Math.min(...dp[Len-1]) 即可实现。

四、代码测试:验证逻辑正确性

用前面提到的示例 triangle = [[2],[3,4],[6,5,7],[4,1,8,3]] 测试代码:

  1. 初始化DP数组为 [[0], [0,0], [0,0,0], [0,0,0,0]]

  2. 设置 dp[0][0] = 2

  3. 第1行(i=1):j=0 → dp10 = 2+3=5;j=1 → dp11 = 2+4=6;

  4. 第2行(i=2):j=0 → 5+6=11;j=1 → min(5,6)+5=10;j=2 → 6+7=13;

  5. 第3行(i=3):j=0 → 11+4=15;j=1 → min(11,10)+1=11;j=2 → min(10,13)+8=18;j=3 →13+3=16;

  6. 最后一行最小值为 11,与预期结果一致,代码正确。

五、优化方向:空间复杂度优化

上面的代码空间复杂度是 O(n²)(n为三角形行数),因为我们创建了一个和三角形大小一致的DP数组。但实际上,我们可以优化到 O(n),核心思路是「滚动数组」:

观察状态转移方程,我们发现第 i行的DP值,只依赖于第 i-1 行的DP值,因此不需要保存整个二维数组,只需要一个一维数组,不断覆盖更新即可。

优化后的代码(TypeScript):

typescript 复制代码
function minimumTotal(triangle: number[][]): number {
  const Len = triangle.length;
  let dp: number[] = new Array(Len).fill(0);
  dp[0] = triangle[0][0];
  for (let i = 1; i < Len; i++) {
    // 从后往前更新,避免覆盖上一行未使用的值
    for (let j = i; j >= 0; j--) {
      if (j === 0) {
        dp[j] = dp[j] + triangle[i][j];
      } else if (j === i) {
        dp[j] = dp[j - 1] + triangle[i][j];
      } else {
        dp[j] = Math.min(dp[j], dp[j - 1]) + triangle[i][j];
      }
    }
  }
  return Math.min(...dp);
};

注意:优化后需要「从后往前」更新一维数组,因为如果从前往后更新,会覆盖上一行的 dp[j-1],导致后续计算出错。

六、总结:解题关键与易错点

解题关键

  • 明确DP数组的定义:dp[i][j] 表示到达第 i 行第 j 个元素的最小路径和;

  • 区分边界情况(第一列和最后一列)和中间情况,正确写出状态转移方程;

  • 最终结果是最后一行的最小值。

易错点

  • 忽略边界条件:忘记第一列只能从上层第一列过来,最后一列只能从上层最后一列过来;

  • DP数组初始化错误:第 i 行的长度应该是 i+1,而非固定长度;

  • 空间优化时,从前往后更新一维数组,导致数据覆盖出错。

这道题虽然简单,但却是动态规划思想的典型应用,掌握它的解题逻辑,能为后续解决更复杂的DP问题打下基础。

相关推荐
smj2302_796826525 分钟前
解决leetcode第4033题有效K个不同元素子数组I
数据结构·python·算法·leetcode
咏方舟【长江支流】10 分钟前
【前端2】单据编辑 -订单单据主子表,万能模板行,自定义添加行
前端·前端框架·状态模式·咏方舟-长江支流
qq_4523962313 分钟前
第六篇:《CI/CD 流水线:从前端构建到自动化部署》
前端·ci/cd·自动化
咏方舟【长江支流】16 分钟前
【前端1】单据编辑 -EasyUI/Vue/React/Bootstrap 主流框架实现订单单据主子表显示和编辑比较,哪个你最易入门?
前端·vue.js·easyui·咏方舟-长江支流·userbaodatagrid
m0_5791466519 分钟前
Element UI 表格合并单元格完全指南:从原理到实战
前端·vue.js·elementui
sibylyue23 分钟前
# Web端流媒体JS播放器开源库
前端·javascript·开源
学linux的QQ蛋30 分钟前
Linux 文件 IO:系统调用 open/read/write 完整总结
linux·运维·算法
码视野34 分钟前
基于 Vue3 + Element Plus 的【基于物联网与深度学习的智慧工业园区能耗监测与光储充一体化微电网协同调度系统】设计与实现(附完整源码与PRD)
前端·人工智能·深度学习·物联网·vue3
rannn_11135 分钟前
【力扣hot100】回溯专题|全排列、子集、字母组合、组合总和、括号生成、单词搜索、分割回文串、N皇后
java·算法·leetcode·回溯
铁皮饭盒38 分钟前
网页端, 5.5mb谷歌模型, 识别躯干, 视频都不卡
前端·javascript·后端