【資料結構與演算法】不同路徑 III:使用哈密爾頓路徑演算法實現
阿新 • • 發佈:2021-11-23
【資料結構與演算法】不同路徑 III:使用哈密爾頓路徑演算法實現
Java
不同路徑 III
https://leetcode-cn.com/problems/unique-paths-iii/
解題思路
使用哈密爾頓路徑的方法解決。
圖的深度優先遍歷,在遍歷時通過left變數記錄所有可走的方塊有沒有被遍歷了,如果發現全部遍歷過了,並且是在出口了,那麼就認為我們找到了一條哈密爾頓路徑,返回1,這樣多個遍歷路徑合併的最終結果就是問題的解。有個比較神奇的地方,就是為啥只使用一次深度優先遍歷就能找到所有路徑?這應該歸功於深度優先遍歷的特點,可以嘗試把深度優先遍歷的遍歷樹畫出來,每一個葉子結點到根結點的路徑就是我們深度遍歷到不同終點的路徑了,而每個葉子結點之間的遍歷互不影響~
本演算法我認為有以下幾點是比較特別的:
- 遍歷時用到回溯思想,即遍歷完了之後在本次遍歷路徑肯定不用再去判斷是否被訪問了,所以要給其他遍歷路徑留活路,把它設定為未訪問。
- 使用left變數記錄被訪問的頂點的個數,不需要在判斷時再去遍歷isVisited陣列有沒有被全部訪問了,這裡有點奇怪的是left不需要通過減一進行回溯嗎?答案是肯定的,因為我們方法傳參的特性,你遞迴呼叫後在被呼叫方法里加了一,但是呼叫方法中的left不會變,因為letf是基本資料型別,不會影響到呼叫方法中的值。
- 巧妙利用了深度優先遍歷遍歷樹的特性,一開始看到問題我還在想是不是要把找到的每條路徑儲存下來,用於後面的去重?結果發現深度遍歷的遍歷樹中兩條遍歷路徑是不會相互影響的,所以你只需要遍歷所有的遍歷樹,然後把滿足條件的葉子結點記錄下來並累加到結果中就可以了。
本演算法對遞迴的使用、圖的深度優先遍歷、圖的建模和對哈密爾頓路徑的理解都是不錯的鍛鍊。
還可以嘗試把所有哈密爾頓路徑輸出。
程式碼
class Solution { boolean[][] isVisited; // 訪問陣列 int[][] grid; // 格子的引用 int R, C; // 行數,列數 int start, end; // 開始位置,結束位置 int[][] dir4 = { // 上,下,左,右 4方向變數 {1,0}, {0,-1}, {0,1}, {-1,0} }; public int uniquePathsIII(int[][] grid) { this.grid = grid; R = grid.length; // 行 C = grid[0].length; // 列 isVisited = new boolean[R][C]; int left = 0; // 掃描整個地圖,判斷一共有多少個可以走的格子,獲取起點和終點 for (int i = 0; i < R; i++) { for (int j = 0; j < C; j++) { if (grid[i][j] == 0) { left++; } else if (grid[i][j] == 1) { start = i * C + j;left++; } else if (grid[i][j] == 2) { end = i * C + j;left++; } } } // 深度優先遍歷來獲取路徑數量 return dfs(start, left); } private int dfs(int v, int left) { int x = v / C; int y = v % C; isVisited[x][y] = true; left--; if (left == 0) { isVisited[x][y] = false; // 回溯,給其他路徑留活路 if(v == end) { return 1; } return 0; // 找到了一條哈密爾頓路徑 } int res = 0; // 獲取相鄰頂點 for (int[] ints : dir4) { int x1 = x + ints[0]; int y1 = y + ints[1]; if (x1 >= 0 && y1 >= 0 && x1 < R && y1 < C && grid[x1][y1] != -1 && !isVisited[x1][y1]) { res += dfs(x1 * C + y1, left); } } isVisited[x][y] = false; // 回溯 return res; } }