矩阵中的最长递增路径

标签: 深度优先搜索 广度优先搜索 拓扑排序 记忆化搜索 数组 动态规划 矩阵

难度: Hard

给定一个 m x n 整数矩阵 matrix ,找出其中 最长递增路径 的长度。

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

示例 1:

输入:matrix = [[9,9,4],[6,6,8],[2,1,1]]
输出:4 
解释:最长递增路径为 [1, 2, 6, 9]

示例 2:

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

示例 3:

输入:matrix = [[1]]
输出:1

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 200
  • 0 <= matrix[i][j] <= 231 - 1

注意:本题与主站 329 题相同: https://leetcode-cn.com/problems/longest-increasing-path-in-a-matrix/

Submission

运行时间: 116 ms

内存: 17.0 MB

class Solution:
    def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
        m = len(matrix)
        n = len(matrix[0])
        outdegree = [[0 for j in range(n)] for i in range(m)]
        queue = deque([])
        for i in range(m):
            for j in range(n):
                now = matrix[i][j]
                if (i - 1 >= 0) and (matrix[i - 1][j] > now):
                    outdegree[i][j] += 1
                if (j - 1 >= 0) and (matrix[i][j - 1] > now):
                    outdegree[i][j] += 1
                if (i + 1 < m) and (matrix[i + 1][j] > now):
                    outdegree[i][j] += 1
                if (j + 1 < n) and (matrix[i][j + 1] > now):
                    outdegree[i][j] += 1
                if not outdegree[i][j]:
                    queue.append((i, j))
        ans = 0
        while queue:
            ans += 1
            for q in range(len(queue)):
                i, j = queue.popleft()
                now = matrix[i][j]
                if (i - 1 >= 0) and (matrix[i - 1][j] < now):
                    outdegree[i - 1][j] -= 1
                    if not outdegree[i - 1][j]:
                        queue.append((i - 1, j))
                if (j - 1 >= 0) and (matrix[i][j - 1] < now):
                    outdegree[i][j - 1] -= 1
                    if not outdegree[i][j - 1]:
                        queue.append((i, j - 1))
                if (i + 1 < m) and (matrix[i + 1][j] < now):
                    outdegree[i + 1][j] -= 1
                    if not outdegree[i + 1][j]:
                        queue.append((i + 1, j))
                if (j + 1 < n) and (matrix[i][j + 1] < now):
                    outdegree[i][j + 1] -= 1
                    if not outdegree[i][j + 1]:
                        queue.append((i, j + 1))
        return ans

Explain

该题解采用了拓扑排序的思想,将矩阵中的每个单元格视为图中的一个节点,如果一个单元格的值小于它相邻的单元格,则在这两个单元格之间存在一条有向边。因此,该问题转化为了找出这个有向图中的最长路径。首先,我们计算每个单元格的出度(即有多少条边从该单元格指向其他单元格),并将出度为0的单元格(即没有任何边指向其他单元格的单元格)加入队列中。然后,我们开始进行拓扑排序,每次从队列中取出一个单元格,并将其相邻单元格的出度减1,如果某个相邻单元格的出度变为0,则将其加入队列中。每次从队列中取出单元格时,路径长度加1。重复这个过程,直到队列为空,此时的路径长度即为最长递增路径的长度。

时间复杂度: O(mn)

空间复杂度: O(mn)

class Solution:
    def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
        m = len(matrix)
        n = len(matrix[0])
        outdegree = [[0 for j in range(n)] for i in range(m)]
        queue = deque([])
        for i in range(m):
            for j in range(n):
                now = matrix[i][j]
                # 计算每个单元格的出度
                if (i - 1 >= 0) and (matrix[i - 1][j] > now):
                    outdegree[i][j] += 1
                if (j - 1 >= 0) and (matrix[i][j - 1] > now):
                    outdegree[i][j] += 1
                if (i + 1 < m) and (matrix[i + 1][j] > now):
                    outdegree[i][j] += 1
                if (j + 1 < n) and (matrix[i][j + 1] > now):
                    outdegree[i][j] += 1
                if not outdegree[i][j]:
                    queue.append((i, j))
        ans = 0
        while queue:
            ans += 1
            for q in range(len(queue)):
                i, j = queue.popleft()
                now = matrix[i][j]
                # 更新相邻单元格的出度
                if (i - 1 >= 0) and (matrix[i - 1][j] < now):
                    outdegree[i - 1][j] -= 1
                    if not outdegree[i - 1][j]:
                        queue.append((i - 1, j))
                if (j - 1 >= 0) and (matrix[i][j - 1] < now):
                    outdegree[i][j - 1] -= 1
                    if not outdegree[i][j - 1]:
                        queue.append((i, j - 1))
                if (i + 1 < m) and (matrix[i + 1][j] < now):
                    outdegree[i + 1][j] -= 1
                    if not outdegree[i + 1][j]:
                        queue.append((i + 1, j))
                if (j + 1 < n) and (matrix[i][j + 1] < now):
                    outdegree[i][j + 1] -= 1
                    if not outdegree[i][j + 1]:
                        queue.append((i, j + 1))
        return ans

Explore

在实现拓扑排序时,我通过条件检查来处理矩阵的边界问题。具体来说,每当需要检查一个单元格的相邻单元格时,我会首先验证这个相邻单元格是否存在,即它的索引是否在合法的范围内。例如,如果要检查一个单元格上方的单元格,则会确认`i - 1 >= 0`;检查左侧单元格时确认`j - 1 >= 0`;检查下方单元格时确认`i + 1 < m`;检查右侧单元格时确认`j + 1 < n`。这样的条件判断确保了不会访问到矩阵的外部,避免了数组越界的错误。

题解中选择将出度为0的单元格加入队列是因为这些单元格没有任何后续单元格,即没有更大的值可以从这些单元格继续递增。这是递增路径的终点。从这些终点开始,进行逆向拓扑排序,可以确保每次处理时,所有被考虑到的单元格都是当前路径的最长可能延伸。这种方法反向追踪递增路径,与常规拓扑排序的方向相反,常规拓扑排序通常从入度为0的节点开始,即没有依赖的起点开始。在这个问题中,我们关注的是最长递增路径的终结,因此从出度为0的单元格开始更合适。

在拓扑排序的实现中,我通过在每个循环中处理整个当前队列来适当增加路径长度。具体而言,每当队列中的单元格被完全处理(即该层的所有单元格都被取出)后,路径长度才会增加1。这是因为路径长度的增加代表了递增路径中的一个层级的完成。在处理过程中,每次循环处理当前队列中的所有单元格,然后再将新的出度变为0的单元格添加到队列中。这保证了在每个单元格被处理时,它所依赖的所有单元格已经被处理过,符合拓扑排序中层级递增的逻辑。