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

img

题目:

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

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

示例:

  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]。注意不允许在对角线方向上移动。

抛砖引玉

img

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

  • 起点:变量矩阵,分别设坐标(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 栏目
欢迎关注留言

公号: 坑人的小书童

坑人的小书童

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
探险家小扣的行动轨迹,都将保存在记录仪。expeditions[i] 表示小扣第 i 次探险记录,用一个字符串数组表示。其的每个「营地」由大小写字母组成,通过子串 -> 连接。例:"Leet->code->Campsite",表示到访了 "Leet"、"code"、"Campsite" 三个营地。expeditions[0] 包含了初始小扣已知的所有营地;对于之后的第 i 次探险(即 expeditions[i] 且 i > 0),如果记录包含了之前均没出现的营地,则表示小扣 新发现 的营地。 请你找出小扣发现新营地最多且索引最小的那次探险,并返回对应的记录索引。如果所有探险记录都没有发现新的营地,返回 -1。注意: 大小写不同的营地视为不同的营地; 营地的名称长度均大于 0。用python实现。给你几个例子:示例 1: 输入:expeditions = ["leet->code","leet->code->Campsite->Leet","leet->code->leet->courier"] 输出:1 解释: 初始已知的所有营地为 "leet" 和 "code" 第 1 次,到访了 "leet"、"code"、"Campsite"、"Leet",新发现营地 2 处:"Campsite"、"Leet" 第 2 次,到访了 "leet"、"code"、"courier",新发现营地 1 处:"courier" 第 1 次探险发现的新营地数量最多,因此返回 1。示例 2: 输入:expeditions = ["Alice->Dex","","Dex"] 输出:-1 解释: 初始已知的所有营地为 "Alice" 和 "Dex" 第 1 次,未到访任何营地; 第 2 次,到访了 "Dex",未新发现营地; 因为两次探险均未发现新的营地,返回 -1
04-23
我的理解是,你想让我编写一段 Python 代码,实现一个算法,用于在给定的探险记录找到发现新营地最多且索引最小的探险记录。 那么首先,我需要解析题目的输入输出要求。 输入要求: - expeditions:一个字符串数组,表示小扣第一次到访的所有营地,和之后的每次探险记录(一个记录就是一个字符串,其营地通过子串 -> 连接)。 输出要求: - 如果所有的探险记录都没有发现新的营地,则返回 -1; - 否则,返回发现新营地最多且索引最小的探险记录的索引。注:探险记录的索引从 0 开始计数。 接下来,我可以考虑如何编写代码来解决这个问题。 我的思路是,首先用一个集合记录所有已知的营地名称,然后遍历每个探险记录,依次将其的营地名称加入到集合,并判断是否有新的营地被发现。 如果某个探险记录发现了新的营地,那么我就记录下发现了多少个新营地,以及这个探险记录的索引。最后,遍历所有的探险记录,找到发现新营地最多且索引最小的记录。 代码如下: ```python def find_new_camp(expeditions): known_camps = set() # 已知的营地集合 max_new_camps = 0 # 最多的新营地数量 max_new_camps_index = -1 # 最多新营地数量的探险记录的索引 for i in range(len(expeditions)): sub_camps = expeditions[i].split("->") # 提取探险记录所有的营地名称 num_new_camps = 0 # 记录这个探险记录发现的新营地数量 for camp in sub_camps: if camp not in known_camps: # 如果这个营地是新的,就记录一下 known_camps.add(camp) num_new_camps += 1 if num_new_camps > max_new_camps: # 如果发现了更多的新营地,就更新最大值和索引 max_new_camps = num_new_camps max_new_camps_index = i return max_new_camps_index if max_new_camps > 0 else -1 ``` 我使用了两个变量 `max_new_camps` 和 `max_new_camps_index` 来记录已经遍历过的探险记录,发现了最多的新营地数量,以及具有最多新营地数量的探险记录的索引。然后我遍历所有的探险记录,统计其发现的新营地数量,如果大于当前的最大值,就更新 `max_new_camps` 和 `max_new_camps_index` 变量。最后,如果发现了新的营地,就返回 `max_new_camps_index`。如果所有的探险记录都没有发现新营地,就返回 -1。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值