Hard
题目描述
给你一个 n x n 整数矩阵 grid,请你返回 非零偏移下降路径 数字和的最小值。
非零偏移下降路径 定义为:从 grid 数组中的每一行选择一个元素,且按顺序选出来的元素中,相邻行选出的元素不在同一列。
示例 1:
输入:grid = [[1,2,3],[4,5,6],[7,8,9]]
输出:13
解释:
可能的下降路径有:
[1,5,9], [1,5,7], [1,6,7], [1,6,8],
[2,4,8], [2,4,9], [2,6,7], [2,6,8],
[3,4,8], [3,4,9], [3,5,7], [3,5,9]
下降路径中数字和最小的是 [1,5,7],所以答案是 13 。
示例 2:
输入:grid = [[7]]
输出:7
提示:
n == grid.length == grid[i].length1 <= n <= 200-99 <= grid[i][j] <= 99
解题思路
这是一道动态规划优化问题。核心在于如何高效处理"相邻行不能选择同一列"的约束。
基本思路:
使用 dp[i][j] 表示到第 i 行第 j 列的最小路径和。状态转移方程为:
dp[i][j] = grid[i][j] + min(dp[i-1][k]) (其中 k ≠ j)
朴素解法: 时间复杂度 O(n³),对每个位置遍历上一行所有不同列的元素。
优化思路: 观察到对于每一行,我们只需要知道上一行的两个信息:
- 最小值及其列索引
- 次小值(当前列恰好是最小值所在列时使用)
这样可以将时间复杂度优化到 O(n²)。
算法步骤:
- 初始化第一行的 dp 值
- 对于每一行,维护上一行的最小值和次小值
- 根据当前列是否为最小值所在列,选择对应的值进行状态转移
- 更新当前行的最小值和次小值信息
- 返回最后一行的最小值
代码实现
class Solution {
public:
int minFallingPathSum(vector<vector<int>>& grid) {
int n = grid.size();
if (n == 1) return grid[0][0];
vector<int> dp(n);
for (int j = 0; j < n; j++) {
dp[j] = grid[0][j];
}
for (int i = 1; i < n; i++) {
// 找到上一行的最小值和次小值
int min1 = INT_MAX, min2 = INT_MAX;
int minIdx = -1;
for (int j = 0; j < n; j++) {
if (dp[j] < min1) {
min2 = min1;
min1 = dp[j];
minIdx = j;
} else if (dp[j] < min2) {
min2 = dp[j];
}
}
vector<int> newDp(n);
for (int j = 0; j < n; j++) {
if (j == minIdx) {
newDp[j] = grid[i][j] + min2;
} else {
newDp[j] = grid[i][j] + min1;
}
}
dp = newDp;
}
return *min_element(dp.begin(), dp.end());
}
};
class Solution:
def minFallingPathSum(self, grid: List[List[int]]) -> int:
n = len(grid)
if n == 1:
return grid[0][0]
dp = grid[0][:]
for i in range(1, n):
# 找到上一行的最小值和次小值
min_vals = sorted(enumerate(dp), key=lambda x: x[1])
min1_idx, min1_val = min_vals[0]
min2_val = min_vals[1][1]
new_dp = [0] * n
for j in range(n):
if j == min1_idx:
new_dp[j] = grid[i][j] + min2_val
else:
new_dp[j] = grid[i][j] + min1_val
dp = new_dp
return min(dp)
public class Solution {
public int MinFallingPathSum(int[][] grid) {
int n = grid.Length;
if (n == 1) return grid[0][0];
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 = int.MaxValue, min2 = int.MaxValue;
int minIdx = -1;
for (int j = 0; j < n; j++) {
if (dp[j] < min1) {
min2 = min1;
min1 = dp[j];
minIdx = j;
} else if (dp[j] < min2) {
min2 = dp[j];
}
}
int[] newDp = new int[n];
for (int j = 0; j < n; j++) {
if (j == minIdx) {
newDp[j] = grid[i][j] + min2;
} else {
newDp[j] = grid[i][j] + min1;
}
}
dp = newDp;
}
return dp.Min();
}
}
var minFallingPathSum = function(grid) {
const n = grid.length;
let dp = [...grid[0]];
for (let i = 1; i < n; i++) {
const newDp = new Array(n).fill(Infinity);
for (let j = 0; j < n; j++) {
for (let k = 0; k < n; k++) {
if (j !== k) {
newDp[j] = Math.min(newDp[j], dp[k] + grid[i][j]);
}
}
}
dp = newDp;
}
return Math.min(...dp);
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n²) - 外层循环 n 次,内层每次遍历 n 个元素找最小值和次小值 |
| 空间复杂度 | O(n) - 使用一维数组存储 dp 状态 |
相关题目
- . Minimum Falling Path Sum (Medium)