1. 问题背景与题目解析
LeetCode 1289题"下降路径最小和 II"是一道经典的动态规划问题,要求我们在给定的二维整数数组中找到一条下降路径,使得路径上的数字之和最小。与传统的下降路径问题不同,这道题的难点在于路径选择的限制条件更加严格。
1.1 题目具体要求
给定一个n x n的二维整数数组grid,我们需要找到一条从第一行到最后一行(即从顶部到底部)的路径,其中:
- 从第i行的某个元素移动到第i+1行的元素时,不能位于同一列
- 路径的和是所有可能路径中最小的
例如,对于输入:
code复制[[1,2,3],
[4,5,6],
[7,8,9]]
最小下降路径和为13(路径1→5→7或1→4→8)。
1.2 问题难点分析
这道题与标准下降路径问题的主要区别在于:
- 列限制:不能连续两行选择同一列的元素
- 时间复杂度:直接暴力解法的时间复杂度为O(n^n),显然不可行
- 空间优化:如何在有限的空间内存储必要的信息
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动态规划解法详解
2.1 基础DP思路
最直观的动态规划解法是定义一个二维数组dp,其中dp[i][j]表示从顶部到达(i,j)位置时的最小路径和。状态转移方程为:
code复制dp[i][j] = grid[i][j] + min(dp[i-1][k]),其中k != j
这种解法的时间复杂度为O(n^3),空间复杂度为O(n^2)。
2.2 优化思路
我们可以观察到,对于每一行,我们只需要知道前一行的最小值和次小值即可,不需要存储整个dp数组。这样可以将空间复杂度优化到O(n),时间复杂度优化到O(n^2)。
具体实现步骤:
- 初始化两个变量min1和min2,分别存储前一行的最小值和次小值
- 遍历每一行时,更新当前行的最小值和次小值
- 如果当前列与前一行的最小值列相同,则使用次小值
2.3 代码实现
java复制public int minFallingPathSum(int[][] grid) {
int n = grid.length;
int[] dp = new int[n];
// 初始化第一行
for (int j = 0; j < n; j++) {
dp[j] = grid[0][j];
}
for (int i = 1; i < n; i++) {
// 找到前一行的最小值和次小值
int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE;
int minIndex = -1;
for (int j = 0; j < n; j++) {
if (dp[j] < min1) {
min2 = min1;
min1 = dp[j];
minIndex = j;
} else if (dp[j] < min2) {
min2 = dp[j];
}
}
// 更新当前行
int[] temp = new int[n];
for (int j = 0; j < n; j++) {
if (j != minIndex) {
temp[j] = grid[i][j] + min1;
} else {
temp[j] = grid[i][j] + min2;
}
}
dp = temp;
}
// 找出最后一行中的最小值
int res = Integer.MAX_VALUE;
for (int num : dp) {
res = Math.min(res, num);
}
return res;
}
3. 算法优化与性能分析
3.1 时间复杂度分析
优化后的算法时间复杂度为O(n^2),因为:
- 外层循环遍历n-1行
- 内层两次遍历n列(找最小值和更新当前行)
3.2 空间复杂度分析
空间复杂度为O(n),因为我们只使用了两个长度为n的数组(dp和temp)和一些常数空间。
3.3 进一步优化
可以进一步优化空间,只使用一个数组,通过巧妙的更新顺序避免使用临时数组:
java复制public int minFallingPathSum(int[][] grid) {
int n = grid.length;
int[] dp = grid[0].clone();
for (int i = 1; i < n; i++) {
int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE;
int minIndex = -1;
for (int j = 0; j < n; j++) {
if (dp[j] < min1) {
min2 = min1;
min1 = dp[j];
minIndex = j;
} else if (dp[j] < min2) {
min2 = dp[j];
}
}
for (int j = 0; j < n; j++) {
if (j != minIndex) {
dp[j] = grid[i][j] + min1;
} else {
dp[j] = grid[i][j] + min2;
}
}
}
int res = Integer.MAX_VALUE;
for (int num : dp) {
res = Math.min(res, num);
}
return res;
}
4. 常见错误与调试技巧
4.1 边界条件处理
- 当n=1时,直接返回grid[0][0]
- 当n=2时,不能选择同一列的元素
4.2 初始化问题
确保dp数组初始化为第一行的值,而不是全0或全最大值。
4.3 最小值更新逻辑
在更新min1和min2时,注意先更新min2再更新min1,否则会丢失原来的min1值。
4.4 测试用例建议
建议测试以下情况:
- 所有元素相同
- 每行都是递增或递减
- 最小路径需要交替选择不同列
- 大n情况下的性能测试
5. 类似题目推荐
- LeetCode 931. 下降路径最小和(标准版)
- LeetCode 120. 三角形最小路径和
- LeetCode 64. 最小路径和
- LeetCode 174. 地下城游戏
这些题目都涉及网格中的路径优化问题,可以帮助巩固动态规划在矩阵路径问题中的应用。
6. 实际应用场景
这类问题在实际中有广泛的应用,例如:
- 游戏中的最优路径规划
- 图像处理中的接缝裁剪(Seam Carving)
- 资源分配优化问题
- 网络路由选择
理解这类问题的解法可以帮助我们在面对类似的实际问题时快速找到解决方案。
