一天一大 leet(矩阵中的最长递增路径)难度:困难-Day20200726

时间:2022-07-25
本文章向大家介绍一天一大 leet(矩阵中的最长递增路径)难度:困难-Day20200726,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目:

给定一个整数矩阵,找出最长递增路径的长度。

对于每个单元格,你可以往上,下,左,右四个方向移动。你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

示例:

  1. 示例 1
输入: nums =
[
  [9,9,4],
  [6,6,8],
  [2,1,1]
]
输出: 4
解释: 最长递增路径为 [1, 2, 6, 9]。
  1. 示例 2
输入: nums =
[
  [3,4,5],
  [3,2,6],
  [2,2,1]
]
输出: 4
解释: 最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。

抛砖引玉

  • 之前的题目都已知起点,而且路径方向限制了只有两个方向,但是,任意单元格可以向上下左右四个方向移动且不知道起点

那把本题向已经做过的题变化一下:

  • 起点:变量矩阵,分别设坐标(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
}