Leetcode 329. Longest Increasing Path in a Matrix

题目:

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为矩阵规模。尽管没有分析出时间复杂度,但是我认为修改后的搜索效率是可以接收的。

你可能感兴趣的:(Leetcode 329. Longest Increasing Path in a Matrix)