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.
大意:给定一个矩阵,要在这个矩阵中找出最长的递增路径的长度。
思路:
基本方法就是深度优先搜索。如果只是用深度优先搜索不加修改,那么需要以矩阵中的所有点为根节点进行一次深搜,深搜过程中更新最长路径的长度,最后得到最长路径长度。
在DFS基础上,假设一个结点已经访问过并且出栈了,那么这个节点的最长路径长度就确定了。基于此,以后再访问这个结点时,就不用继续搜索下去,直接使用该节点的最长路径长度来更新父节点的最长路径长度即可。
考虑点(i, j),有两种情况,能继续扩展和无法继续。
如果无法扩展,那么就要回溯到(i,j)前一个结点(b_i, b_j),此时(i,j)的最长路径长度已经确定,此时可以更新(b_i,b_j)的最长路径长度:要么是原来的长度,要么是(i,j)的最长长度加1。
如果可以扩展,假设下一个待选择的结点是(f_i, f_j),那么有三种情况。(f_i, f_j)是前面的结点之一或者不是递增的,跳过,寻找其他结点;待选结点递增且不是前面的结点,但曾经访问过,说明它的最长长度已知,此时不必继续搜索下去了,更新(i,j)的最长长度即可,然后寻找其他结点;待选结点递增而且从未访问过,选定该结点继续搜索下一个结点。
这样,对大部分结点只搜索一次,但为了更新父节点最长路径长度,部分已访问过的结点还是会被访问多次的。然而,相较于纯粹的DFS,明显减少了许多不必要的搜索,最明显的就是很多搜索过的结点不会再作为根节点。
代码:
#include<iostream> #include<vector> #include<fstream> #include<iomanip> using namespace std; int longest(vector<vector<int> > &matrix) { if(matrix.size() == 0) return 0; const int m = matrix.size(), n = matrix[0].size(), SIZE = m*n; //len用来记录每个点的最长路径长度,v记录每个结点访问的情况,dic记录栈中对应结点最后扩展的方向,s用来模仿栈 int len[SIZE], v[SIZE], dic[SIZE] = {0}, s[SIZE][2] = {0}; //maxlen记录整个矩阵的最长路径长度,count用来记录栈顶位置,tot记录搜索的根节点的位置 int maxlen = 0, i, j, count = 0, tot = 0, x, y, temp_i, temp_j; bool out = true; //用来标记搜索结束 //初始化len和v for (int k = 0; k < SIZE; k++) { len[k] = 1; v[k] = -1; } v[0] = 0; while(true) { temp_i = i = s[count][0]; // 取栈顶 temp_j = j = s[count][1]; if(dic[count] == 0) { // 向上 if(i-1>=0) i--; else dic[count]++; } if(dic[count] == 1) { // 向右 if (j+1<n) j++; else dic[count]++; } if (dic[count] == 2) { // 向下 if (i+1 < m) i++; else dic[count]++; } if (dic[count] == 3) { // 向左 if (j-1>=0) j--; else dic[count]++; } if(dic[count] > 3) { // 无路可走,进行回溯 dic[count] = 0; // 该结点即将出栈,对应的方向应该重置 if (count == 0) { // 当前结点是根节点 out = true; if (len[i*n+j] > maxlen) maxlen = len[i*n+j]; while(++tot < SIZE) { // 寻找下一个根节点 if(v[tot] == -1) { // 选择未访问过的根节点 v[tot] = tot; x = s[count][0] = tot/n; // 替换根节点 y = s[count][1] = tot%n; out = false; // 还需要继续搜索 break; } } if(out) return maxlen; // 没有结点需要继续搜索,退出 continue; } else { // 当前结点不是根节点,更新父结点最长长度,该结点出栈 temp_i = i; temp_j = j; v[i*n+j] = -2; // 表示该结点曾经访问过,但已出栈 i = s[--count][0]; // 出栈操作 j = s[count][1]; if (1 + len[temp_i*n+temp_j] > len[i*n+j]) len[i*n+j] = 1 + len[temp_i*n+temp_j]; if (len[i*n+j] > maxlen) maxlen = len[i*n+j]; dic[count]++; // 更新父节点的方向,指向下一个待选结点 continue; } } if (v[i*n+j] == tot || matrix[i][j] <= matrix[temp_i][temp_j]) { // 结点在栈中或者非递增 dic[count]++; continue; } else if (v[i*n+j] != -1) { // 递增且不在栈中,但曾经访问过 dic[count]++; if (len[temp_i*n+temp_j] < 1 + len[i*n+j]) // 更新当前结点的最长路径长度 len[temp_i*n+temp_j] = 1 + len[i*n+j]; if (len[temp_i*n+temp_j] > maxlen) maxlen = len[temp_i*n+temp_j]; continue; } else { // 递增且从文访问过,入栈 s[++count][0] = i; s[count][1] = j; v[i*n+j] = tot; dic[count] = 0; continue; } } } // 用到test-longest.txt,文档内容附在测试样例中 int main() { int m, n; ifstream input; input.open("test-longest.txt"); input >> m; input >> n; vector<int> ini(n, 0); vector<vector<int> > matrix(m, ini); for (int i = 0; i < m; i++) for(int j = 0; j < n; j++) { input >> matrix[i][j]; } input.close(); ofstream output; output.open("test-longest.txt"); output << m << ' ' << n <<endl; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) output << setw(4) << matrix[i][j]; output << endl; } output.close(); cout << longest(matrix) << endl; return 0; }测试样例1(结果为10):
19 15 15 19 1 1 19 0 3 2 4 2 6 10 16 18 8 16 11 0 13 19 12 2 14 13 6 5 14 19 5 16 18 7 8 17 7 2 8 0 12 14 18 5 16 19 0 18 9 14 5 3 7 14 0 3 16 17 10 4 4 8 11 6 2 7 0 5 1 16 7 14 9 10 6 17 2 2 5 8 8 5 10 15 14 1 6 15 12 19 10 13 7 10 5 18 4 11 1 17 14 8 6 2 10 19 6 9 16 11 15 14 7 18 0 4 6 0 15 15 14 11 12 5 9 8 2 18 9 13 14 5 18 2 13 15 14 7 4 5 7 19 0 6 15 16 16 6 7 18 2 8 6 17 10 3 10 15 10 4 17 15 11 7 9 13 13 15 6 8 14 10 18 14 13 9 19 19 15 6 17 7 15 8 7 6 14 16 16 15 2 2 7 0 0 14 8 14 2 1 10 17 6 3 1 0 1 7 1 11 15 4 17 1 16 4 11 8 14 19 13 15 9 1 17 12 13 15 18 17 11 12 14 18 14 13 16 8 17 5 15 12 18 15 18 18 18 16 4 9 0 8 16 1 5 5 9 8 2 16 7 13 0 13 13 11 4 15 8 19 12 1 6 6 2 7 17 10 16 16 19 3 10 4 9 6 11
样例2(结果为6):
7 7 18 17 14 11 10 15 14 16 18 0 17 17 18 12 2 18 18 7 7 15 17 18 7 10 6 14 2 14 16 18 11 15 12 0 9 17 15 13 13 19 17 16 0 9 10 0 5 1 7
复杂度分析:
时间复杂度暂时没有头绪,空间复杂度为O(m*n),m*n为矩阵规模。尽管没有分析出时间复杂度,但是我认为修改后的搜索效率是可以接收的。