给定一个三角形 triangle
,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i
,那么下一步可以移动到下一行的下标 i
或 i + 1
。
示例 1:
输入: triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出: 11
解释: 如下面简图所示:
2
3 4
6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
示例 2:
输入: triangle = [[-10]]
输出: -10
提示:
1 <= triangle.length <= 200
triangle[0].length == 1
triangle[i].length == triangle[i - 1].length + 1
-10⁴ <= triangle[i][j] <= 10⁴
进阶:
O(n)
的额外空间(n
为三角形的总行数)来解决这个问题吗?我们可以进行问题拆解,将问题拆解为更小的问题,利用更小问题的最优解来得到原问题的解。要计算到达某行的最小路径和,可以先算出到达前一行各个结点的最小路径和,然后基于这一结果可以算出到达本行各个结点的最小路径和,最终取其中的最小值,这也就是动态规划算法。所以我们只需要从第 1 行开始,通过该方法依次算出到达第 2、3、4… 行各节点最小路径和,算完最后一行即可取得答案。这样我们就把为原问题拆解为了多个小规模的子问题,并且子问题间是存在重叠,需要依赖其他子问题的计算结果,这样也可以减少重复的计算。接下来我们需要先捋一下动态规划的状态转移方程。
状态转移方程
由前面的解释可以得到状态转移方程:
f(i,j) = min(f(i-1,j), f(i-1,j-1)) + triangle[i][j]
其中,f(i,j) 表示移动到第 i 行第 j 个结点的最小路径和,i、j 均从 0 开始算;triangle[i][j] 表示三角形中的节点。初始值
方程的初始值即为 f(0,0) = triangle[0][0]
其他边界值
在转移方程中,当 j = 0,或者 i = j 时部分值是没有意义的,
当 j = 0,简化为:f(i,j) = f(i-1,j) + triangle[i][j];
当 i = j,简化为:f(i,j) = f(i-1,j-1) + triangle[i][j]。
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int size = triangle.size();
int[][] dp = new int[size][size];
dp[0][0] = triangle.get(0).get(0);
for (int i = 1; i < size; i++) {
dp[i][0] = dp[i - 1][0] + triangle.get(i).get(0);
for (int j = 1; j < i; j++) {
// 公式
// f(i,j) = min(f(i-1,j), f(i-1,j-1)) + triangle.get(i).get(j);
dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle.get(i).get(j);
}
dp[i][i] = dp[i - 1][i - 1] + triangle.get(i).get(i);
}
int res = dp[size - 1][0];
for (int i = 1; i < size; i++) {
res = Math.min(res, dp[size - 1][i]);
}
return res;
}
}
复杂度分析
进阶-空间优化
考虑如何把空间复杂度缩小到 O(n)?
实际上 dp 定义为一个长度为 n 的一维数组即可满足使用,要改变的是对于每行遍历列时需要从右到左进行遍历,为什么要这样呢?
因为如果是从左到右的话,当获取当前行的 dp[j] 的时候,就会把上一行保留的 dp[j] 结果给覆盖点,这样
就没法算 dp[j + 1] 了;
但是若从右到左,计算过程中给 dp 赋值的位置都是后面待计算结点不用依赖的,不会发生冲突。
优化后代码
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int size = triangle.size();
int[] dp = new int[size];
dp[0] = triangle.get(0).get(0);
for (int i = 1; i < size; i++) {
dp[i] = dp[i - 1] + triangle.get(i).get(i);
for (int j = i - 1; j > 0; j--) {
// 公式
// f(i,j) = min(f(i-1,j), f(i-1,j-1)) + triangle.get(i).get(j);
dp[j] = Math.min(dp[j], dp[j - 1]) + triangle.get(i).get(j);
}
dp[0] = dp[0] + triangle.get(i).get(0);
}
int res = dp[0];
for (int i = 1; i < size; i++) {
res = Math.min(res, dp[i]);
}
return res;
}
}
复杂度分析