题目地址:
https://leetcode.com/problems/shortest-path-in-a-hidden-grid/
给定一个未知的迷宫,提供一个类GridMaster的对象master,其能实现以下操作:
1、boolean canMove(char direction)
返回能否走这个方向,方向一共有四种,'U', 'D', 'L', 'R'
;
2、void move(char direction)
朝这个方向走一步,如果这个方向不能走(即遇到了障碍物),则停留在原地;
3、boolean isTarget()
返回当前是否在目标位置。
一开始的位置是起点位置,题目保证起点和目标都不是障碍物。问从起点到目标点的最短距离。每一步走的距离视为
1
1
1。
可以认为该迷宫是个矩形网格,因为每一步只能走四个方向,由平移对称性,可以设初始点为 ( 0 , 0 ) (0,0) (0,0)。由于每走一步,master会真实地走到那个位置,所以一开始不方便用BFS。可以先用DFS把整个迷宫每个格子是空地还是障碍物给建出来,如果找到了目标点,则标记一下目标点的坐标。接着可以用A*算法求一下最短路。A*算法的相关概念可以参考https://blog.csdn.net/qq_46105170/article/details/114877098。这里的启发函数可以取成当前位置到目标点的曼哈顿距离,然后建最小堆,按 f ( x ) = g ( x ) + h ( x ) f(x)=g(x)+h(x) f(x)=g(x)+h(x)的从小到大的顺序出堆, g g g代表从起点到 x x x的真实走过的距离, h h h是启发函数。由A*算法的性质,当终点第一次出堆的时候,其距离是真实最短距离。代码如下:
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.PriorityQueue;
public class Solution {
class Pair {
int x, y, f;
public Pair(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
Pair pair = (Pair) o;
return x == pair.x && y == pair.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
private Pair target;
public int findShortestPath(GridMaster master) {
char[] d = {'U', 'R', 'D', 'L'};
int[] dir = {-1, 0, 1, 0, -1};
Pair start = new Pair(0, 0);
Map<Pair, Integer> map = new HashMap<>();
dfs(start, map, master, d, dir);
return target == null ? -1 : bfs(start, map, dir);
}
private int bfs(Pair start, Map<Pair, Integer> map, int[] dir) {
start.f = heur(start, target);
PriorityQueue<Pair> minHeap = new PriorityQueue<>((p1, p2) -> Integer.compare(p1.f, p2.f));
minHeap.offer(start);
Map<Pair, Integer> dist = new HashMap<>();
dist.put(start, 0);
while (!minHeap.isEmpty()) {
Pair cur = minHeap.poll();
if (cur.equals(target)) {
return dist.get(cur);
}
for (int i = 0; i < 4; i++) {
Pair next = new Pair(cur.x + dir[i], cur.y + dir[i + 1]);
if (map.get(next) != 0) {
if (!dist.containsKey(next) || dist.get(next) > dist.get(cur) + 1) {
dist.put(next, dist.get(cur) + 1);
next.f = dist.get(next) + heur(next, target);
minHeap.offer(next);
}
}
}
}
return -1;
}
private int heur(Pair cur, Pair target) {
return Math.abs(cur.x - target.x) + Math.abs(cur.y - target.y);
}
// 从起点DFS,标记每个点是空地1,目标点2,还是障碍物0
private void dfs(Pair cur, Map<Pair, Integer> map, GridMaster master, char[] d, int[] dir) {
if (master.isTarget()) {
map.put(cur, 2);
target = cur;
} else {
map.put(cur, 1);
}
int x = cur.x, y = cur.y;
for (int i = 0; i < 4; i++) {
int nextX = x + dir[i], nextY = y + dir[i + 1];
Pair next = new Pair(nextX, nextY);
if (map.containsKey(next)) {
continue;
}
if (master.canMove(d[i])) {
master.move(d[i]);
dfs(next, map, master, d, dir);
// 回溯回来的时候回退一格
master.move(d[(i + 2) % 4]);
} else {
map.put(next, 0);
}
}
}
}
时空复杂度 O ( m n ) O(mn) O(mn), m n mn mn是迷宫的行数和列数。