題目
給定一個(gè) n 行 m?列矩陣 matrix?,矩陣內(nèi)所有數(shù)均為非負(fù)整數(shù)。 你需要在矩陣中找到一條最長路徑,使這條路徑上的元素是遞增的。并輸出這條最長路徑的長度。
這個(gè)路徑必須滿足以下條件:
- 對(duì)于每個(gè)單元格,你可以往上,下,左,右四個(gè)方向移動(dòng)。 你不能在對(duì)角線方向上移動(dòng)或移動(dòng)到邊界外。
- 你不能走重復(fù)的單元格。即每個(gè)格子最多只能走一次。
數(shù)據(jù)范圍:1≤n,m≤1000,0≤matrix[i][j]≤1000。
進(jìn)階:空間復(fù)雜度?O(nm),時(shí)間復(fù)雜度?O(nm)。
例如:當(dāng)輸入為[[1,2,3],[4,5,6],[7,8,9]]時(shí),對(duì)應(yīng)的輸出為5,其中的一條最長遞增路徑如下圖所示:
示例1
輸入:[[1,2,3],[4,5,6],[7,8,9]]
返回值:5
說明:1->2->3->6->9即可。當(dāng)然這種遞增路徑不是唯一的。
示例2
輸入:[[1,2],[4,3]]
返回值:4
說明:?1->2->3->4
備注:矩陣的長和寬均不大于1000,矩陣內(nèi)每個(gè)數(shù)不大于1000。
思路1:深度優(yōu)先搜索(dfs)(推薦使用)
深度優(yōu)先搜索一般用于樹或者圖的遍歷,其他有分支的(如二維矩陣)也適用。
它的原理是從初始點(diǎn)開始,一直沿著同一個(gè)分支遍歷,直到該分支結(jié)束,然后回溯到上一級(jí)繼續(xù)沿著一個(gè)分支走到底,如此往復(fù),直到所有的節(jié)點(diǎn)都有被訪問到。
既然是查找最長的遞增路徑長度,那我們首先要找到這個(gè)路徑的起點(diǎn),起點(diǎn)不好直接找到,就從上到下從左到右遍歷矩陣的每個(gè)元素。然后以每個(gè)元素都可以作為起點(diǎn)查找它能到達(dá)的最長遞增路徑。
如何查找以某個(gè)點(diǎn)為起點(diǎn)的最長遞增路徑呢?我們可以考慮深度優(yōu)先搜索,因?yàn)槲覀儾檎疫f增路徑的時(shí)候,每次選中路徑一個(gè)點(diǎn),然后找到與該點(diǎn)相鄰的遞增位置,相當(dāng)于進(jìn)入這個(gè)相鄰的點(diǎn),繼續(xù)查找遞增路徑,這就是遞歸的子問題。因此遞歸過程如下:
- 終止條件:?進(jìn)入路徑最后一個(gè)點(diǎn)后,四個(gè)方向要么是矩陣邊界,要么沒有遞增的位置,路徑不能再增長,返回上一級(jí)。
- 返回值:?每次返回的就是本級(jí)之后的子問題中查找到的路徑長度加上本級(jí)的長度。
- 本級(jí)任務(wù):?每次進(jìn)入一級(jí)子問題,先初始化后續(xù)路徑長度為0,然后遍歷四個(gè)方向(可以用數(shù)組表示,下標(biāo)對(duì)數(shù)組元素的加減表示去往四個(gè)方向),進(jìn)入符合不是邊界且在遞增的鄰近位置作為子問題,查找子問題中的遞增路徑長度。因?yàn)橛兴膫€(gè)方向,所以最多有四種遞增路徑情況,因此要維護(hù)當(dāng)級(jí)子問題的最大值。
具體做法:文章來源:http://www.zghlxwxcb.cn/news/detail-448796.html
- step 1:使用一個(gè)dp數(shù)組記錄i,j處的單元格擁有的最長遞增路徑,這樣在遞歸過程中如果訪問到就不需要重復(fù)訪問。
- step 2:遍歷矩陣每個(gè)位置,都可以作為起點(diǎn),并維護(hù)一個(gè)最大的路徑長度的值。
- step 3:對(duì)于每個(gè)起點(diǎn),使用dfs查找最長的遞增路徑:只要下一個(gè)位置比當(dāng)前的位置數(shù)字大,就可以深入,同時(shí)累加路徑長度。
代碼1
import java.util.*;
public class Solution {
//記錄4個(gè)方向
private int[][] dirs = new int[][] {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
private int n, m;
public int solve (int[][] matrix) {
//邊界條件判斷
if (matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int res = 0;
n = matrix.length;
m = matrix[0].length;
//j,j處的單元格擁有的最長遞增路徑
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
//更新最大值
res = Math.max(res, dfs(matrix, dp, i, j));
}
}
return res;
}
//深度優(yōu)先搜索,返回最大單元格數(shù)
public int dfs(int[][] matrix, int[][] dp, int i, int j) {
if (dp[i][j] != 0) {
return dp[i][j];
}
dp[i][j]++;
for(int k = 0; k < 4; k++) {
int nexti = i + dirs[k][0];
int nextj = j + dirs[k][1];
//判斷下一個(gè)要遍歷的位置是否滿足條件:在矩陣范圍內(nèi)且滿足路徑遞增
if(nexti >= 0 && nexti < n && nextj >= 0 && nextj < m && matrix[nexti][nextj] > matrix[i][j]) {
dp[i][j] = Math.max(dp[i][j], dfs(matrix, dp, nexti, nextj) + 1);
}
}
return dp[i][j];
}
}
- 時(shí)間復(fù)雜度:O(mn),m、n分別為矩陣的兩邊,遍歷整個(gè)矩陣以每個(gè)點(diǎn)作為起點(diǎn),然后遞歸相當(dāng)于遍歷填充dp矩陣。
- 空間復(fù)雜度:O(mn),輔助矩陣的空間是一個(gè)二維數(shù)組。
思路2:廣度優(yōu)先搜索(bfs)
廣度優(yōu)先搜索與深度優(yōu)先搜索不同,它是將與某個(gè)節(jié)點(diǎn)直接相連的其它所有節(jié)點(diǎn)依次訪問一次之后,再往更深處,進(jìn)入與其他節(jié)點(diǎn)直接相連的節(jié)點(diǎn)。
bfs的時(shí)候我們常常會(huì)借助隊(duì)列的先進(jìn)先出,因?yàn)閺哪硞€(gè)節(jié)點(diǎn)出發(fā),我們將與它直接相連的節(jié)點(diǎn)都加入隊(duì)列,它們優(yōu)先進(jìn)入,則會(huì)優(yōu)先彈出,在它們彈出的時(shí)候再將與它們直接相連的節(jié)點(diǎn)加入,由此就可以依次按層訪問。
我們可以將矩陣看成是一個(gè)有向圖,一個(gè)元素到另一個(gè)元素遞增,代表有向圖的箭頭。這樣我們可以根據(jù)有向圖的出度入度找到最長的路徑,且這個(gè)路徑在矩陣中就是遞增的。
具體做法:
- step 1:計(jì)算每個(gè)節(jié)點(diǎn)(單元格)所對(duì)應(yīng)的出度(符合邊界條件且遞增),對(duì)于作為邊界條件的單元格,它的值比所有的相鄰單元格的值都要大,因此作為邊界條件的單元格的出度都為0。利用一個(gè)二維矩陣記錄每個(gè)單元格的出度
- step 2:利用拓?fù)渑判虻乃枷?,從所有出度?的單元格開始進(jìn)行廣度優(yōu)先搜索。
- step 3:借助隊(duì)列來廣度優(yōu)先搜索,隊(duì)列中每次加入出度為0的點(diǎn),即路徑最遠(yuǎn)點(diǎn),每次從A點(diǎn)到B點(diǎn),便將A點(diǎn)出度減一。
- step 4:每次搜索都會(huì)遍歷當(dāng)前層的所有單元格,更新其余單元格的出度,并將出度變?yōu)?的單元格加入下一層搜索。
- step 5:當(dāng)搜索結(jié)束時(shí),搜索的總層數(shù)即為矩陣中的最長遞增路徑的長度,因?yàn)閎fs的層數(shù)就是路徑增長的層數(shù)。
代碼文章來源地址http://www.zghlxwxcb.cn/news/detail-448796.html
import java.util.*;
public class Solution {
//記錄4個(gè)方向
private int[][] dirs = new int[][] {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
private int n, m;
public int solve (int[][] matrix) {
//邊界條件判斷
if (matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int res = 0;
n = matrix.length;
m = matrix[0].length;
//j,j處的單元格擁有的最長遞增路徑
int[][] outdegrees = new int[m + 1][n + 1];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < 4; k++) {
int nexti = i + dirs[k][0];
int nextj = j + dirs[k][1];
if (nexti >= 0 && nexti < n && nextj >= 0 && nextj < m &&
matrix[nexti][nextj] > matrix[i][j]) {
//符合條件,記錄出度
outdegrees[i][j]++;
}
}
}
}
//輔助隊(duì)列
Queue<Integer> q1 = new LinkedList<Integer>();
Queue<Integer> q2 = new LinkedList<Integer>();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (outdegrees[i][j] == 0) {
//找到出度為0的入隊(duì)
q1.offer(i);
q2.offer(j);
}
}
}
while (!q1.isEmpty()) {
res++;
int size = q1.size();
for (int x = 0; x < size; x++) {
int i = q1.poll();
int j = q2.poll();
//四個(gè)方向
for (int k = 0; k < 4; k++) {
int nexti = i + dirs[k][0];
int nextj = j + dirs[k][1];
//逆向搜索,所以下一步是小于
if (nexti >= 0 && nexti < n && nextj >= 0 && nextj < m &&
matrix[nexti][nextj] < matrix[i][j]) {
//符合條件,出度遞減
outdegrees[nexti][nextj]--;
if (outdegrees[nexti][nextj] == 0) {
q1.offer(nexti);
q2.offer(nextj);
}
}
}
}
}
return res;
}
}
- 時(shí)間復(fù)雜度:O(mn),m、n分別為矩陣的兩邊,相當(dāng)于遍歷整個(gè)矩陣兩次。
- 空間復(fù)雜度:O(mn),輔助矩陣的空間是一個(gè)二維數(shù)組。
到了這里,關(guān)于BM61-矩陣最長遞增路徑的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!