我正在研究下面的算法:
Given an integer matrix, find the length of the longest increasing path.
From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).
Example 1:
nums = [
[9,9,4],
[6,6,8],
[2,1,1]
]
Return 4
The longest increasing path is [1, 2, 6, 9].
Example 2:
nums = [
[3,4,5],
[3,2,6],
[2,2,1]
]
Return 4
The longest increasing path is [3, 4, 5, 6]. Moving diagonally is not allowed.
我最初的冲动是在每个方向上有4个具有增加值的函数 . 例如,如果整数1在[1,1]中,并且围绕该单元格的四个值增加,则将创建三个函数 . 这个过程,对于最坏情况下的每个单元,我相信是O(4 ^(M * N)[每个元素有4个函数调用,并且有mxn元素] .
但是,解决方案提出了以下蛮力(在他们继续通过memoization进行优化之前):
Algorithm
Each cell can be seen as a vertex in a graph G. If two adjacent cells have value a < ba<b, i.e. increasing then we have a directed edge (a, b)(a,b). The problem then becomes:
Search the longest path in the directed graph G.
Naively, we can use DFS or BFS to visit all the cells connected starting from a root. We update the maximum length of the path during search and find the answer when it finished.
Usually, in DFS or BFS, we can employ a set visited to prevent the cells from duplicate visits. We will introduce a better algorithm based on this in the next section.
Java
// Naive DFS Solution
// Time Limit Exceeded
public class Solution {
private static final int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private int m, n;
public int longestIncreasingPath(int[][] matrix) {
if (matrix.length == 0) return 0;
m = matrix.length;
n = matrix[0].length;
int ans = 0;
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
ans = Math.max(ans, dfs(matrix, i, j));
return ans;
}
private int dfs(int[][] matrix, int i, int j) {
int ans = 0;
for (int[] d : dirs) {
int x = i + d[0], y = j + d[1];
if (0 <= x && x < m && 0 <= y && y < n && matrix[x][y] > matrix[i][j])
ans = Math.max(ans, dfs(matrix, x, y));
}
return ++ans;
}
}
该算法的时间复杂度非常相似(在最坏的情况下,对于单个单元启动4个dfs函数)具有以下复杂性分析:
Complexity Analysis
Time complexity : O(2^{m+n))
). The search is repeated for each valid increasing path. In the worst case we can have O(2^{m+n)) calls. For example:
1 2 3 . . . n
2 3 . . . n+1
3 . . . n+2
. .
. .
. .
m m+1 . . . n+m-1
Space complexity : O(mn). For each DFS we need O(h) space used by the system stack, where hh is the maximum depth of the recursion. In the worst case, O(h) =O(mn).
我没有关注他们如何获得这种时间复杂性或空间复杂性 . 对于空间成本,我想最糟糕的情况是矩阵按行和列的升序排序,但单个堆栈将与mxn平方的对角线长度大致成比例 . 这就是为什么空间最坏的情况与O(mn)成正比?另外,空间如何花费O(2 ^(m n))而不是O(4 ^(m * n)?
1 回答
我相信Sunny是正确的,强力搜索的时间复杂度为O(
4^(m*n)
),其中n
和m
是与给定数组关联的矩阵中的行数和列数 . 我想建议一个O(n*m
)算法和实现它的Ruby代码 .在诸如此类的问题中,趋势是考虑从矩阵中的每个元素移动到所有相邻的较大值元素(最多4个),然后从这些元素中的每个元素移动到它们所有相邻的较大值元素,以及等等(因此O(
4^m*n
)) . 但是,由于路径必须增加,我们可以通过不同的方式查看问题,从而使我们能够开发出高效的优化算法 .假设我们按值对所有位置进行分组 . 对于问题中给出的示例,可以表达:
接下来让我们按递减顺序考虑哈希的值:
首先,我们使用值
9
处理这两个位置 . 从这些位置中的任何一个位置增加路径的长度显然是1
(路径为[[0, 0]]
和[[1, 0]]
),因为无处可去 . 我们将此信息保存到以前为空的哈希processed
,现在是:接下来考虑具有值
8
(to_process
的第二个元素),[1, 2]
的单个位置 . 如果来自此位置的增加路径的长度大于1
,则必须接下来转到processed
的其中一个元素 .[1, 2]
既不与[0, 0]
也不与[1, 1]
相邻,所以我们添加了键值对:到
processed
,获得要检查的下一个值(在
to_process
中)是6
,它发生在两个位置[1, 0]
和[1, 1]
. 前者与processed
([0, 0]
)中的一个位置相邻,因此我们将processed
添加为键值对值为
6
,[1, 1]
的另一个元素在processed
,[0, 1]
和[1, 2]
中有两个相邻元素,因此请添加要么
到
processed
,即:len
的值最大的那个 . (这里,它是一个平局,所以要么可以选择 . )我们继续这种方式,直到原始数组的所有元素都是
processed
中的键 . 然后,我们选择processed[loc][:len]
最大的位置loc
作为最长增加路径的起点,并重建相关路径 .注意,仅需要键
:next
来重建最长路径 . 如果仅需要最长路径的长度,则不需要该密钥 .Code
Examples
#1
#2
因此,最长路径的长度为
5
,并且包含4
元素,然后是[6, 3]
,然后是5
,最多是6
,右边是7
,右边是8
.#3
Explanation
首先让我们考虑辅助方法
greater_neighbors
,例如#1 . 对于arr
,如图所示,接下来,(将
arr
视为矩阵)我们将具有相同值的位置分组:此哈希将包含已检查的位置 .
这提供了处理给定值的位置的顺序,从
high
向下到low
.第一个元素生成
enum0
并传递给块:和
执行,所以我们不执行
next
.我们现在考虑所有位置的值为
9
,然后是值为8
的位置,依此类推 .在生成
9
并将其传递给块后,并且未执行next
时,将执行以下计算:方法
greater_neighbors
只是构造一个与loc
相邻的所有位置的数组,其值大于loc
处的值 .我们现在生成
enum1
的下一个和最后一个元素并将其传递给块:此位置的计算与
[0, 0]
的计算类似,导致:我们已经到达了
enum1
的最后一个元素,所以我们接下来执行:再一次,这个地方无处可去,所以我们得到:
继续,
最后,我们来到了一个与更高 Value 的位置(
[0, 0]
)相邻的位置([1, 0]
) .这种计算以这种方式继续,直到我们获得:
剩下的就是找到
v[:len]
最大的键值对k=>v
,然后提取最长的路径 . 这是由助手extract
完成的,与greater_neighbors
一样,很简单 .