三角形最小路径和 图解题解
这道题到底在问什么
- 输入
- triangle=[[2],[3,4],[6,5,7],[4,1,8,3]]
- 输出
- 11
最优解:为什么这么做
一句话答案:LeetCode 120 三角形最小路径和:自底向上动态规划 dp[j]=triangle[i][j]+min(dp[j],dp[j+1]),从底行往上填每格恰有两条来路,省掉自顶向下的边界特判,一维滚动,时间 O(n²)、空间 O(n)。
三角形最小路径和到底在求什么
给一个数字三角形 triangle,从顶上出发,每步只能走到下一行紧挨着的两个数之一——正下方或右下方,走到最底行,求这一路数字之和最小。以 [[2],[3,4],[6,5,7],[4,1,8,3]] 为例,走 2→3→5→1 得 11,是所有走法里最小的,答案就是 11。
为什么不能把每条路径都走一遍
最直接的想法是把每条路径都走一遍比出最小的。但每往下一行都要二选一,n 行三角形有约 2 的 n-1 次方条路径,指数级膨胀、枚举不完。慢在同一个格子被反复经过,往下那段路在无数条路径里一遍遍重算。既然每个格子往下的最优走法固定,算一次存下来复用,这就是动态规划——算过的子问题记下复用、不再重算。
为什么反过来从最底行往上填
不从顶往下算,而是从最底行倒着往上推。定义 dp[j] 为「当前这一行第 j 个格子往下走到底的最小路径和」。最底行的格子已经到底,最小和就是自己。往上一行行覆盖,算上面某格时它下面正铺着刚算完的一行。
好处是每个非底行的格子都规整地有正下方、右下方两条来路。若从顶往下填,每行头尾只有一条来路(贴着斜边),要单独写边界判断;倒着从底往上,这类首尾特判整个消失。
本格加下方两格较小,为什么能压成一行
转移式(由已算好的格子推出当前格子的公式)就一句:dp[j] = triangle[i][j] + min(dp[j], dp[j+1]),本格值加上正下方、右下方里更小的那个。下标别弄反:正下方是 dp[j]、右下方是 dp[j+1],同一个 j 差一位。
算当前行第 j 格只用到下一行的 dp[j] 和 dp[j+1],再往下的行用不上了。所以不必存整张二维表,只需一个长度等于行数的一维数组,从底行起每算一行就地覆盖旧值。这种把二维表滚成一行的写法叫滚动数组,空间从 O(n²) 压到 O(n)。
拿这个三角形把每一格亲手填一遍
拿 [[2],[3,4],[6,5,7],[4,1,8,3]] 走一遍。最底行 [4,1,8,3] 各自到底,dp 起始就是 4、1、8、3。往上一行 [6,5,7]:6 的下方是 4 和 1 取 1,记 6+1=7;5 的下方是 1 和 8 取 1,记 5+1=6;7 的下方是 8 和 3 取 3,记 7+3=10。这行 dp 是 7、6、10。
再上一行 [3,4]:3 的下方是 7 和 6 取 6,记 3+6=9;4 的下方是 6 和 10 取 6,记 4+6=10,dp 是 9、10。顶行 [2]:2 的下方是 9 和 10 取 9,记 2+9=11。填到顶格得 11,就是这个三角形的最小路径和。
复杂度是多少,两个下标和答案位置别搞错
三角形约 n²/2 个格子,每格一次取小、一次加法都是常数,时间 O(n²);一维滚动只留一行,空间 O(n)。
两处最容易出错。一是下标:正下方 dp[j]、右下方 dp[j+1],写成 dp[j-1] 或取反就顺着错路加。二是答案位置:填完后答案是顶格 dp[0],它汇总了全部路径,不是最底行里最小的数——底行的值只是各自起点。数带负号也照样成立。
▶ 动画逐步走查(共 23 步)——想跟着动画一帧帧对照就展开
- 3记住这条「本格 + 下方两格较小」,下面每格都在套它。
- 4先把三角形左对齐摆进表里(右边空位是三角形外,没有格子)。我们从最底一行开始往上填。
- 5先看最底行第 0 格:它已经到底,没有下一步可走。
- 6所以它的最小路径和就是自己 = 4。
- 7先看最底行第 1 格:它已经到底,没有下一步可走。
- 8所以它的最小路径和就是自己 = 1。
- 9先看最底行第 2 格:它已经到底,没有下一步可走。
- 10所以它的最小路径和就是自己 = 8。
- 11先看最底行第 3 格:它已经到底,没有下一步可走。
- 12所以它的最小路径和就是自己 = 3。
- 13算第 2 行第 0 格:本格 6,下方两条路是 4 和 1,挑更小的 1。
- 146 + 1 = 7,填进第 2 行第 0 格。走「右下方」更省。
- 15算第 2 行第 1 格:本格 5,下方两条路是 1 和 8,挑更小的 1。
- 165 + 1 = 6,填进第 2 行第 1 格。走「正下方」更省。
- 17算第 2 行第 2 格:本格 7,下方两条路是 8 和 3,挑更小的 3。
- 187 + 3 = 10,填进第 2 行第 2 格。走「右下方」更省。
- 19算第 1 行第 0 格:本格 3,下方两条路是 7 和 6,挑更小的 6。
- 203 + 6 = 9,填进第 1 行第 0 格。走「右下方」更省。
- 21算第 1 行第 1 格:本格 4,下方两条路是 6 和 10,挑更小的 6。
- 224 + 6 = 10,填进第 1 行第 1 格。走「正下方」更省。
- 23算第 0 行第 0 格:本格 2,下方两条路是 9 和 10,挑更小的 9。
- 242 + 9 = 11,填进第 0 行第 0 格。走「正下方」更省。
- 25填到顶格 dp[0][0] = 11,这就是从顶到底的最小路径和。
⚠️ 容易写错的地方
✗ 错:自顶向下找最小
✓ 对:自底向上更省事
自顶向下要处理边界(每行首尾只有一条来路),自底向上每格都规整有两条
✗ 错:min 取错下标
✓ 对:下方=dp[j],右下=dp[j+1]
同一个 j,下方两格下标是 j 和 j+1
✗ 错:答案取最底行最小
✓ 对:答案是填到顶的 dp[0]
自底向上后顶格已汇总全部路径
完整代码(Python / C++ / Java)
Python
def minimumTotal(triangle):
dp = triangle[-1][:] # 最底行
for i in range(len(triangle)-2, -1, -1):
for j in range(i+1):
dp[j] = triangle[i][j] + min(dp[j], dp[j+1])
return dp[0]C++
int minimumTotal(vector<vector<int>>& t){
vector<int> dp = t.back();
for(int i = t.size()-2; i >= 0; i--)
for(int j = 0; j <= i; j++)
dp[j] = t[i][j] + min(dp[j], dp[j+1]);
return dp[0];
}Java
int minimumTotal(List<List<Integer>> t){
int n = t.size();
int[] dp = new int[n + 1];
for(int i = n - 1; i >= 0; i--)
for(int j = 0; j <= i; j++)
dp[j] = t.get(i).get(j) + Math.min(dp[j], dp[j + 1]);
return dp[0];
}复杂度
时间
O(n²)
三角形共 n²/2 个格,每格 O(1)
空间
O(n)
滚动一维 dp,长度 = 行数
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 三角形最小路径和 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么自底向上比自顶向下好写?+
自底向上时,每个非底行的格子都恰好有正下方、右下方两条来路,转移统一成 dp[j]=triangle[i][j]+min(dp[j],dp[j+1]),不用为任何格子写例外。自顶向下则每行的第一个和最后一个格子只有一条来路(它们贴着三角形的两条斜边),得单独判断、单独处理,代码里多出好几个边界分支,也更容易漏判。两种方向答案一样,自底向上只是省心。
空间压到 O(n) 时,一维数组会不会把还要用的值覆盖掉?+
不会。算当前行的 dp[j] 只依赖下一行的 dp[j] 和 dp[j+1],再下面的行早就汇进这两个值了。按 j 从小到大就地更新时,dp[j] 被新值覆盖之前,它右边的 dp[j+1] 还是下一行的旧值,正好够用;等这一行填完,下一行的数据也就用完可以丢了。所以只需一个长度等于行数的数组反复覆盖,空间 O(n)。
答案为什么是顶格 dp[0],不是最底行里最小的那个?+
dp[j] 的定义是「从这个格子往下走到底的最小和」。自底向上填到最顶层时,dp[0] 已经把从顶到底所有路径的比较结果汇总进去了,它就是全局最小路径和。最底行的每个 dp 只是那个格子自己的值、还没往上汇总,取它们的最小只得到底行的一个数,跟整条路径的最小和无关。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 三角形最小路径和 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。