本篇只考虑无权图的最短路径。
问题一:走迷宫问题
方案一(dfs + 回溯):
方案二(bfs):
问题二:单词接龙(leetcode127)
解法一(dfs + 回溯):
解法二(bfs获得最短路径):
定义一个二维数组M*N maze
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。入口点maze[0][0] = 0,出口点maze[M - 1][N - 1] = 0表示出口入口都是可以走的。
很容易想到的一个方案就是让他沿一条路径往下走,走不通再回溯到上一个分叉路口,如此再获得所有可达路径后返回其中长度最短的路径。实现代码如下:
public class Main{
public static class Node{
int x;
int y;
public Node(int x, int y){
this.x = x;
this.y = y;
}
public String toString(){
return "(" + x + "," + y + ")";
}
}
static int[][] directs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
static int[][] maze = null;
static boolean[][] access = null;
static List result = null;
public static void main(String[] g){
Scanner in = new Scanner(System.in);
while(in.hasNext()){
int M = in.nextInt(), N = in.nextInt();
maze = new int[M][N];
access = new boolean[M][N];
result = null;
for(int i = 0; i < M; i++){
for(int j = 0; j < N; j++){
maze[i][j] = in.nextInt();
}
}
dfs(0, 0, new Stack());
for(Node node : result){
System.out.println(node);
}
}
}
public static void dfs(int i, int j, Stack stack){
if(i == maze.length - 1 && j == maze[0].length - 1){
if(result == null || stack.size() < result.size()){
stack.push(new Node(i, j));
result = new ArrayList<>(stack);
stack.pop();
}
return;
}
if(i < 0 || i >= maze.length || j < 0 || j >= maze[0].length ||
access[i][j] || maze[i][j] == 1){
return;
}
access[i][j] = true;
stack.push(new Node(i, j));
for(int[] direct : directs){
dfs(i + direct[0], j + direct[1], stack);
}
stack.pop();
access[i][j] = false;
}
}
方案一有个最大的缺点就是需要获得所有的路径,时间复杂度过高。
一种合理的方案是使用bfs一层一层的往外扩,首次扩到的终点即为所求。不过需要注意的是求最短路径的bfs和一般bfs最大不同是每次一层一层的访问,即出队前先获得当前层的结点个数levelNum,该轮只往出弹levelNum个元素。我们将当前结点记做cur、和其相连的结点记做next,若next未被访问则从出发点到next的最短路径中,cur为next的前驱结点。
实现代码如下:
import java.util.*;
public class Main{
public static class Node{
int x;
int y;
public Node(int x, int y){
this.x = x;
this.y = y;
}
public String toString(){
return "(" + x + "," + y + ")";
}
}
static int[][] directs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public static void main(String[] g){
Scanner in = new Scanner(System.in);
while(in.hasNext()){
int M = in.nextInt(), N = in.nextInt();
int[][] maze = new int[M][N];
for(int i = 0; i < M; i++){
for(int j = 0; j < N; j++){
maze[i][j] = in.nextInt();
}
}
Node[][] path = process(maze);
Stack stack = new Stack<>();
stack.push(new Node(M - 1, N - 1));
Node cur = path[M - 1][N - 1];
while(!(cur.x == 0 && cur.y == 0)){
stack.push(cur);
cur = path[cur.x][cur.y];
}
stack.push(cur);
while(!stack.isEmpty()){
System.out.println(stack.pop());
}
}
}
public static Node[][] process(int[][] maze){
int M = maze.length, N = maze[0].length;
boolean[][] access = new boolean[M][N];
Node[][] path = new Node[M][N];//存储该路径上的前一个元素
Queue queue = new LinkedList<>();
access[0][0] = true;
queue.add(new Node(0, 0));
while(!queue.isEmpty()){
int levelNum = queue.size();
for(int i = 0; i < levelNum; i++){
Node cur = queue.remove();
for(int[] direct : directs){
int x = cur.x + direct[0];
int y = cur.y + direct[1];
if(x < 0 || x >= M || y < 0 || y >= N || maze[x][y] == 1 || access[x][y]){
continue;
}
access[x][y] = true;
path[x][y] = cur;
queue.add(new Node(x, y));
if(x == M - 1 && y == N - 1){
return path;
}
}
}
}
return null;
}
}
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-ladder
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度 5。
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: 0
解释: endWord "cog" 不在字典中,所以无法进行转换。
解法和问题一的走迷宫问题如出一辙,使用dfs获得所有能从beginWord到endWord的路径,然后取最短的。不过在OJ中超时了。实现代码如下:
class Solution {
String beginWord;
String endWord;
Map wordSet;
int result = Integer.MAX_VALUE;
public int ladderLength(String beginWord, String endWord, List wordList) {
this.beginWord = beginWord;
this.endWord = endWord;
this.wordSet = new HashMap<>();
for(String word : wordList){
wordSet.put(word, false);
}
wordSet.put(beginWord, false);
dfs(beginWord, 0);
return result == Integer.MAX_VALUE ? 0 : result;
}
public void dfs(String cur, int sum){
sum++;
if(cur.equals(endWord)){
result = Math.min(result, sum);
}
if(wordSet.get(cur)){
return ;
}
wordSet.put(cur, true);
for(int i = 0; i < cur.length(); i++){
char[] array = cur.toCharArray();
for(char j = 'a'; j <= 'z'; j++){
array[i] = j;
if(!wordSet.containsKey(String.valueOf(array))){
continue;
}
dfs(String.valueOf(array), sum);
}
}
wordSet.put(cur, false);
}
}
主要看了weiwie哥的题解,学习到了使用bfs获得无权图的最短路径。主要就是一次只遍历同一层的(即他们距出发位置是相同的)。
代码如下:
class Solution {
public int ladderLength(String beginWord, String endWord, List wordList) {
Set wordSet = new HashSet<>(wordList);
Set access = new HashSet<>();
Queue queue = new LinkedList<>();
queue.add(beginWord);
int step = 1;
while(!queue.isEmpty()){
int leveNum = queue.size(); // 当前层节点个数
for(int i = 0; i < leveNum; i++){
String cur = queue.remove();
access.add(cur);
char[] temp = cur.toCharArray();
for(int j = 0; j < temp.length; j++){
char record = temp[j]; //记录修改前的字符
for(char k = 'a'; k <= 'z'; k++){
temp[j] = k;
String nextWord = String.valueOf(temp);
if(!wordSet.contains(nextWord) || access.contains(nextWord)){
continue;
}
if(nextWord.equals(endWord)){
return step + 1;
}
access.add(nextWord);
queue.add(nextWord);
}
temp[j] = record; // 恢复
}
}
step++;
}
return 0;
}
}