【数据结构与算法】不同路径 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;
}
}