来源:力扣(LeetCode)
描述:
给你一个 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].length
- 1 <= n <= 200
- -99 <= grid[i][j] <= 99
方法一:动态规划
思路与算法
我们可以使用动态规划来解决这个问题。
令状态 f[i][j] 表示从数组 grid 的前 iii 行中的每一行选择一个数字,并且第 i 行选择的数字为 grid[i][j] 时,可以得到的路径和最小值。f[i][j] 可以从第 i − 1 行除了 f[i−1][j] 之外的任意状态转移而来,因此有如下状态转移方程:
最终,我们取第 n − 1 行中的最小值,即最小的 min(f[n][j]) 作为答案。
代码:
class Solution {
public:
int minFallingPathSum(vector<vector<int>>& grid) {
int n = grid.size();
vector<vector<int>> d(n, vector<int>(n, INT_MAX));
for (int i = 0; i < n; i++) {
d[0][i] = grid[0][i];
}
for (int i = 1; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
if (j == k) {
continue;
}
d[i][j] = min(d[i][j], d[i - 1][k] + grid[i][j]);
}
}
}
int res = INT_MAX;
for (int j = 0; j < n; j++) {
res = min(res, d[n - 1][j]);
}
return res;
}
};
时间760ms 击败 6.61%使用 C++ 的用户
内存15.04mb 击败 32.78%使用 C++ 的用户
复杂度分析
- 时间复杂度:O(n3),其中 n 是 grid 的行数和列数。我们使用三重循环来求解答案,每一层循环的复杂度为 O(n),第 0 层单独求解和最终答案遍历的时间复杂度均为 O(n),因此总的时间复杂度为 O(n3)。
- 空间复杂度:O(n2)。我们使用二维数组 d[i][j] 来存储过程中的答案,实际上可以使用滚动数组优化至 O(n)。
方法二:转移过程优化
思路与算法
不难发现,求解第 i 行中所有的 f[i][j] 时,有很多状态在转移时所依赖的 min(f[i−1][k]) 是相同的。我们令 first_min_sum 为第 i − 1 行状态中的最小值,first_min_index 为最小值对应的下标,second_min_sum 为状态次小值。那么有如下转移方程:
对于 i = 0 的情况,我们不做变动。对于 i ≠ 0 情况,判断当前遍历到的下标 jjj 是否与上一行状态中的最小值下标 first_min_index 相同,若相同,取次小值 second_min_sum 转移,若不同,取 first_min_sum 转移。
因此,我们只需维护第 i − 1 行相关的三个变量(最小值,最小值下标和次小值)就可以在 O(1) 时间内求解 f[i][j]f[i][j]f[i][j]。在求解 f[i][j] 的过程中,我们也只需记录第 iii 行相关的变量即可,不用把所有的 f[i][j]f[i][j]f[i][j] 都保存下来。这样做可以将空间复杂度优化到 O(1)。
代码:
class Solution {
public:
int minFallingPathSum(vector<vector<int>>& grid) {
int n = grid.size();
int first_min_sum = 0;
int second_min_sum = 0;
int first_min_index = -1;
for (int i = 0; i < n; i++) {
int cur_first_min_sum = INT_MAX;
int cur_second_min_sum = INT_MAX;
int cur_first_min_index = -1;
for (int j = 0; j < n; j++) {
int cur_sum = (j != first_min_index ? first_min_sum : second_min_sum) + grid[i][j];
if (cur_sum < cur_first_min_sum) {
cur_second_min_sum = cur_first_min_sum;
cur_first_min_sum = cur_sum;
cur_first_min_index = j;
} else if (cur_sum < cur_second_min_sum) {
cur_second_min_sum = cur_sum;
}
}
first_min_sum = cur_first_min_sum;
second_min_sum = cur_second_min_sum;
first_min_index = cur_first_min_index;
}
return first_min_sum;
}
};
时间 20ms 击败 99.45%使用 C++ 的用户
内存 13.74mb 击败 99.17%使用 C++ 的用户
复杂度分析
- 时间复杂度:O(n2),其中 n 是 grid 的行数和列数。
- 空间复杂度:O(1)。过程中我们只使用了常数个变量。
author:力扣官方题解