LeetCode [329. 矩阵中的最长递增路径]
题目:
给定一个整数矩阵,找出最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。
示例 1:
输入: nums =
[
[9,9,4],
[6,6,8],
[2,1,1]
]
输出: 4
解释: 最长递增路径为 [1, 2, 6, 9]。
示例 2:
输入: nums =
[
[3,4,5],
[3,2,6],
[2,2,1]
]
输出: 4
解释: 最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。
该题也被称为滑雪问题。
思路:
-
状态表示方法
f[i][j]
:走到 i,j 时,路径的大小 -
状态转移:
f[i][j] =max(f[i - 1][j] + 1, f[i][j - 1] + 1, f[i + 1][j] + 1, f[i][j + 1] + 1)
前提可以移动到 i,j -
边界条件,遍历完所有可走路径
记忆化搜索:
设一个辅助数组,记录已经经过的点。这样时间复杂度从指数级别降为 O(N^2)。在本题中可通过初始化状态矩阵 f 为特定值 -1,来使其具有记忆性。
即 -1 是未走过的点,大于 0 为从此点走可以走的最大值
- 设好辅助数组,初始化全为 -1;
- 搜索路径通过递归(地图遍历),每次递归需要计算 4 个方向(上下左右)的情况(四个方位)
- 确定好是否为可行路径(地图的边界)和题目要求只能递增走,符合条件继续递归,不符合条件返回
- 每走一步(进入一次递归),进行 +1
- 和状态矩阵中的数值进行比较,选择最大值
- 递归完成,得到最优解
代码:
class Solution {
public:
vector<vector<int>> f;
int n, m;
// 记忆化搜索
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
int dp(int x, int y, vector<vector<int>>& matrix) // 定义到 x,y 时的状态(路径大小)
{
if(f[x][y] != -1) return f[x][y];
f[x][y] = 1;
for(int i = 0; i < 4; i++)
{
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < n && b >= 0 && b < m && matrix[a][b] > matrix[x][y])
f[x][y] = max(f[x][y], dp(a, b, matrix) + 1);
}
return f[x][y];
}
int longestIncreasingPath(vector<vector<int>>& matrix) {
if(matrix.empty()) return 0;
n = matrix.size(); m = matrix[0].size();
f = vector<vector<int>> (n, vector<int>(m, -1));
int res = 0;
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
res = max(res, dp(i, j, matrix));
return res;
}
};
解析:
以输入为
{ {9, 9, 4}, {6, 6, 8 }, {2, 1, 1} } 为例
matrix | j = 0 | j = 1 | j = 2 |
---|---|---|---|
i = 0 | 9 | 9 | 4 |
i = 1 | 6 | 6 | 8 |
i = 2 | 2 | 1 | 1 |
程序从(0,0)开始遍历。每次以 “上 右 下 左” 进行尝试
f | j = 0 | j = 1 | j = 2 |
---|---|---|---|
i = 0 | -1 | -1 | -1 |
i = 1 | -1 | -1 | -1 |
i = 2 | -1 | -1 | -1 |
程序入口
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
res = max(res, dp(i, j, matrix));
进入第一个递归,即 从(0,0)开始,只有 “右 下”,可以走,但是数值并非递增,
所以,在位置(0,0)上,最多只能走一步。
更新状态方程
f | j = 0 | j = 1 | j = 2 |
---|---|---|---|
i = 0 | 1 | -1 | -1 |
i = 1 | -1 | -1 | -1 |
i = 2 | -1 | -1 | -1 |
四个方向移动
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
状态转移
for(int i = 0; i < 4; i++)
{
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < n && b >= 0 && b < m && matrix[a][b] > matrix[x][y])
f[x][y] = max(f[x][y], dp(a, b, matrix) + 1);
}
从状态转移条件可以看出此处需要将 matrix 传入 dp 函数中,因为每一次需要比较大小
程序进行执行,直到位置达到(0,2),4,直观上我们可以看出,向左(9)和向下(8)都可以走。
程序在状态转移中也如此
- 向上和向右,均出地图边界
- 向下(8),满足所有条件,第一层递归,将(1,2)位置传入dp函数
- f(1,2) = -1,(1,2)没走过
- 在此点至少为路径长度为 1,f(1,2) = 1
- 在(1,2)四个方向中,没有可走的点,所以在此点最大可以走 1 步
- 返回 1
f[x][y] = max(f[x][y], dp(a, b, matrix) + 1);
===》f[x][y] = max(1, 1+1);
- 向左(9),满足所有条件,第一层递归,将(0,1)位置传入dp函数
- f(0,1) = 1,没走过,直接返回该点的值(已经被更新为 1)
f[x][y] = max(f[x][y], dp(a, b, matrix) + 1);
===》f[x][y] = max(2, 1+1);
- 最后 (0,2)更新为 2
f | j = 0 | j = 1 | j = 2 |
---|---|---|---|
i = 0 | 1 | 1 | 2 |
i = 1 | -1 | -1 | -1 |
i = 2 | -1 | -1 | -1 |
以此类推,如果起始从(2,1)点开始怎会进行多层递归处理,每次返回也会将经过路径中的每点的最长递增路径返回。如果从以(0,0,)位置走,也会一点一点将数值填充好
最后结果
f | j = 0 | j = 1 | j = 2 |
---|---|---|---|
i = 0 | 1 | 1 | 2 |
i = 1 | 2 | 2 | 1 |
i = 2 | 3 | 4 | 2 |