题目:
给定一个整数矩阵,找出最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。
你不能在对角线方向上移动或移动到边界外(即不允许环绕)。
示例:
- 示例 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]。注意不允许在对角线方向上移动。
抛砖引玉
- mXn 的矩阵移动的问题之前有个:
- 之前的题目都已知起点,而且路径方向限制了只有两个方向,但是,任意单元格可以向上下左右四个方向移动且不知道起点
那把本题向已经做过的题变化一下:
- 起点:变量矩阵,分别设坐标(i,j)的点为起点
- 之前 dp 记录每个点的结果,本题相邻点的结果没有了推到关系,那指定起点查询以它开始的路线可能
思路
- 查询矩阵中所有点为起点的路线可能
- dp[i][j]存储以(i,j)为起点所有可能路线中最多节点的节点数
- 最终出现的最大可能数即为结果
实现
- 声明 dp 长宽与 matrix 一致
- 给定起点(i,j),查询其四个方向是否满足大于该点位置:
- 如果大于则,节点数= 1+以满足条件位置为起点的最多节点的节点数
- 如果小于则,该路线不通
- 给定起点查询最多节点的节点数时,起点会多次枚举,且枚举起点又设计查询满足条件的其他方位点做起点,则使用递归查询
- 递归优化,出现过的起点直接返回结果
- 终点返回计算的节点数
/**
* @param {number[][]} matrix
* @return {number}
*/
var longestIncreasingPath = function (matrix) {
let row = matrix.length,
colum = matrix[0] ? matrix[0].length : 0,
_result = 0,
dp = Array.from({ length: row }, () => Array(colum).fill(0))
// matrix长宽为0 返回结果值0
if (row === 0 || colum === 0) return _result
// 遍历枚举起点
for (let i = 0; i < row; i++) {
for (let j = 0; j < colum; j++) {
// 变量行列查询到所以可能的起点
_result = Math.max(_result, dfs(i, j))
}
}
// 指定起点坐标查询其最大递增路线
function dfs(r, c) {
// 不为0则什么已经计算
if (dp[r][c]) return dp[r][c]
// 以其自身做起点 默认节点数1
dp[r][c] = 1
// 四个方向
// 左侧
if (c - 1 >= 0 && matrix[r][c - 1] > matrix[r][c])
dp[r][c] = Math.max(dp[r][c], dfs(r, c - 1) + 1)
// 右侧
if (c + 1 < colum && matrix[r][c + 1] > matrix[r][c])
dp[r][c] = Math.max(dp[r][c], dfs(r, c + 1) + 1)
// 上方
if (r - 1 >= 0 && matrix[r - 1][c] > matrix[r][c])
dp[r][c] = Math.max(dp[r][c], dfs(r - 1, c) + 1)
// 下方
if (r + 1 < row && matrix[r + 1][c] > matrix[r][c])
dp[r][c] = Math.max(dp[r][c], dfs(r + 1, c) + 1)
// 返回指定坐标结果
return dp[r][c]
}
return _result
}
拓扑排序
按照上面思路发现其实已经枚举了已所有点为起点路线情况,
既然枚举了所有路线,那某一个节点,一定知道有多少路线包含了它,或者某一个点是否与其他点形成路线,
且已知任何一条路线的终点一定在四个方向上都不能移动的坐标
那么记录索引在四个方向上都不能移动的坐标,再从这个点向起点反推,反推的次数最多的就查找的节点最多的路线,反推的次数就是节点数
/**
* @param {number[][]} matrix
* @return {number}
*/
var longestIncreasingPath = function (matrix) {
let dirs = [
[-1, 0], // 上方
[1, 0], // 下方
[0, -1], // 左侧
[0, 1], // 右侧
],
row = matrix.length,
colum = matrix[0] ? matrix[0].length : 0,
_result = 0,
level = Array.from({ length: row }, () => Array(colum).fill(0)),
dp = []
// matrix长宽为0 返回结果值0
if (row === 0 || colum === 0) return _result
// 计算每个单元格 四个方向上满足条件的方向数
for (let i = 0; i < row; i++) {
for (let j = 0; j < colum; j++) {
for (let k = 0; k < 4; k++) {
let r = i + dirs[k][0],
c = j + dirs[k][1]
if (
r >= 0 &&
r < row &&
c >= 0 &&
c < colum &&
matrix[r][c] > matrix[i][j]
) {
// 记录在所有路线中点(i,j)存在的数量
level[i][j]++
}
}
// 如果点(i,j)为在本路线中出现则记录坐标,作为路线终点
if (level[i][j] === 0) dp.push([i, j])
}
}
// 遍历终点集合,反推起点
while (dp.length > 0) {
// 记录遍历层数
_result++
let dpLen = dp.length
for (let x = 0; x < dpLen; x++) {
let cell = dp.shift(),
i = cell[0],
j = cell[1]
// (i,j)为终点坐标,(r,c)为满足反推条件的起点坐标
for (let k = 0; k < 4; ++k) {
let r = i + dirs[k][0],
c = j + dirs[k][1]
if (
r >= 0 &&
r < row &&
c >= 0 &&
c < colum &&
matrix[r][c] < matrix[i][j]
) {
// 遍历一层则默认以(r,c)起点的可能路线减少一个(及包含终点(i,j)的那一条)
level[r][c]--
// 如果(r,c)起点也不存在路线经过他了,那将其放置到dp中作为终点
if (level[r][c] === 0) {
dp.push([r, c])
}
}
}
}
}
return _result
}
博客: 小书童博客
每天的每日一题,写的题解会同步更新到公众号一天一大 lee 栏目
欢迎关注留言
公号: 坑人的小书童