推箱子1~15关图解求解

推箱子游戏是将箱子推到目标点,是一个经典的游戏,这个问题存在一个最优解,即最短走法,这里通过分析一个箱子与多个箱子,探讨问题的解法。一个箱子问题链接:leetcode推箱子这个问题是一个阉割版,不考虑人走的步数,只考虑箱子被推到目标位置(前提是人不被卡住)思路考虑使用BFS,计算每个点离目标点的可推最短距离(自定义距离,可以推动的最短距离)为了简化计算,还要用一个数组记录已经遍历过的点。 计算箱子所在当前点与目标点的距离,从初始点向四个方向扩展,直到找到目标点(反过来计算也可以),为了避免找到的不是最优解,可以在每个点加入队列时按照移动步数steps进行排序。代码class Solution {
public int minPushBox(char[][] grid) {
int rlen = grid.length;
int llen = grid[0].length;
//visited[i][j][m][n] = true 表示 人物在 (i, j) 坐标和 箱子在 (m, n) 坐标 这个状态已经访问过了
boolean[][][][] visited = new boolean[rlen][llen][rlen][llen];
int sx = -1, sy = -1, tx = -1, ty = -1, bx = -1, by = -1;
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
for (int i = 0; i < rlen; i++) {
for (int j = 0; j < llen; j++) {
if (grid[i][j] == 'S') {
sx = i;
sy = j;
}
if (grid[i][j] == 'T') {
tx = i;
ty = j;
}
if (grid[i][j] == 'B') {
bx = i;
by = j;
}
}
}
/*
当人物在箱子的左边时,人物可以选择向右边走
当人物在箱子的右边时,人物可以选择向左边走
当人物在箱子的上边时,人物可以选择向下边走
当人物在箱子的下边时,人物可以选择向上边走
这样才能保证步数最少,否则,如果箱子在左边,人物还向着右边走,那么就距离箱子越来越远,这是毫无意义的步数
无法满足条件的情况:
如果箱子会自己走的话,那么简单的 bfs 就能够完成了,但是这里需要人物来推动箱子
这意味着箱子可能虽然旁边就是终点,但是可能不存在能够容纳人物的地方来推动箱子
比如 下图,虽然 箱子 B 旁边就是终点 T,如果它能够自己走的话直接一步到终点
但是由于需要推动,而却不存在容纳人物 S 的位置来 箱子 B 到达终点 T
# # # #
# T B #
# . S #
什么时候箱子的位置会发生改变?
当人物向上下两个方向走的时候,如果人物的下一个位置就是箱子的位置,
那么相当于顶着箱子前进,那么箱子同时也要往前进
因为人物的移动不算在步数里的,因此可能移动的时候当前箱子推的步数很大,比如示例 1 ,人物绕了一大圈
如果最优的情况,即最少的推箱子步数就是这绕一大圈的,但是别的状态还在推箱子,它能够更快的到达终点,但是推箱子步数很大
所以最先碰到终点的不一定是步数最少的,所以需要使用一个优先队列
*/
PriorityQueue<Node> queue = new PriorityQueue<>((a, b) -> a.steps - b.steps);
// 按steps进行升序排列,确保找到的一定是最短的
queue.add(new Node(0, sx, sy, bx, by));
// 初始点
while (!queue.isEmpty()) {
Node t = queue.poll();
int currentBoxX = t.bx, currentBoxY = t.by;
if (currentBoxX == tx && currentBoxY == ty) {
// 箱子到了目标点
return t.steps;
}
visited[t.hx][t.hy][currentBoxX][currentBoxY] = true;
//往四个方向走
for (int[] p : directions) {
int newPx = t.hx + p[0];
int newPy = t.hy + p[1];
int newBx = t.bx;
int newBy = t.by;
int newStep = t.steps;
//人物的前进位置刚好是箱子的位置,那么箱子的位置也要发生改变
if (newPx == t.bx && newPy == t.by) {
newBx += p[0];
newBy += p[1];
//箱子动了,步数 +1
newStep++;
}
//越界或者在障碍物上,那么跳过
if (!legalIndex(newBx, rlen, newBy, llen)
!legalIndex(newPx, rlen, newPy, llen)
grid[newPx][newPy] == '#'
grid[newBx][newBy] == '#') {
continue;
}
if (!visited[newPx][newPy][newBx][newBy]) {
queue.add(new Node(newStep, newPx, newPy, newBx, newBy));
}
}
}
return -1;
}
private static boolean legalIndex(int x, int rlen, int y, int llen) {
return x >= 0 && x < rlen && y >= 0 && y < llen;
}
static class Node {
int steps;
int hx;
int hy;
int bx;
int by;
public Node(int steps, int hx, int hy, int bx, int by) {
this.steps = steps;
this.hx = hx;
this.hy = hy;
this.bx = bx;
this.by = by;
}
}
}
然而,这个解法不够智能,只击败了10%的java提交,我们有以下方法可以进行优化使用并查集进行优化class Solution {
//1. 并查集判断能否到达
//2. BFS 箱子
int[][] dir = new int[][]{{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
public int minPushBox(char[][] grid) {
int m = grid.length, n = grid[0].length;
int px = 0, py = 0, tx = 0, ty = 0, bx = 0, by = 0;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(grid[i][j] == 'B') {
bx = i;
by = j;
} else if(grid[i][j] == 'S') {
px = i;
py = j;
} else if(grid[i][j] == 'T') {
tx = i;
ty = j;
}
}
}
int[] parent = buildSet(grid, m, n);
int sp = find(parent, px * n + py);
int st = find(parent, tx * n + ty);
int sb = find(parent, bx * n + by);
if(!(sp == st && st == sb)) return -1;
Queue<int[]> que = new LinkedList<>();
Set<String> visited = new HashSet<>();
que.offer(new int[]{bx * n + by, px * n + py});
visited.add((bx * n + by) + "," + (px * n + py));
int res = 0;
while(!que.isEmpty()) {
for(int size = que.size(); size > 0; size--) {
int[] p = que.poll();
if(p[0] == tx * n + ty) return res;
int r = p[0] / n, c = p[0] % n;
char ch = grid[r][c];
grid[r][c] = '#';
parent = buildSet(grid, m, n);
for(int[] d : dir) {
int x = r + d[0], y = c + d[1];
int rx = r - d[0], ry = c - d[1];
if(x >= 0 && x < m && y >= 0 && y < n && grid[x][y] != '#' && rx >= 0 && rx < m && ry >= 0 && ry < n && grid[rx][ry] != '#') {
if(find(parent, rx * n + ry) == find(parent, p[1]) && visited.add((x * n + y) + "," + p[0])) {
que.offer(new int[]{x * n + y, p[0]});
}
}
}
grid[r][c] = (char)(res + '0');
}
res++;
}
return -1;
}
private int[] buildSet(char[][] grid, int m, int n) {
int[] parent = new int[m * n];
for(int i = 0; i < m * n; i++) parent[i] = i;
for(int i = 0; i < m; i ++) {
for(int j = 0; j < n; j++) {
if(grid[i][j] != '#') {
if(i > 0 && grid[i-1][j] != '#') union(parent, (i-1)*n+j, i*n+j);
if(i < m-1 && grid[i+1][j] != '#') union(parent, (i+1)*n+j, i*n+j);
if(j > 0 && grid[i][j-1] != '#') union(parent, i*n+j-1, i*n+j);
if(j < n-1 && grid[i][j+1] != '#') union(parent, i*n+j+1, i*n+j);
}
}
}
return parent;
}
private void union(int[] p, int a, int b) {
int pa = find(p, a), pb = find(p, b);
if(pa != pb) p[pa] = pb;
}
private int find(int[] p, int a) {
return p[a] = p[a] == a ? a : find(p, p[a]);
}
}
击败了41%的java提交更好的解法可以用启发式搜索对这个寻找过程进行加速。箱子用了A*,估价函数f返回值里,第一比较值是取曼哈顿距离和已走步数之和,保证在箱体内搜索出步数最优值,后续比较值是box的坐标。人用best-first算法,原理和A*相似,只是不用考虑最短步数,只优先考虑距离终点近点的扩展。因为复数不能在堆里直接比较,所以加了time和pTime计数器保证优先处理先进堆的元素,且不会比较到复数坐标。A*算法和BF算法单轮无障碍情况下不大于O((M+N)log(M+N))的时间,对数计算来自于堆的复杂度,单轮有障碍则是O(MNlog(MN)),所以总的最坏时间复杂度则是O(M 2 N 2 log(MN) 2),考虑三个关键点的大多分布在地图周围,但人的行走步数拓展层次多数情况下不会很深,且由于估价函数的存在,大大提升了寻路效率,线上测试平均优化掉一半时间是符合逻辑的。代码:}

我要回帖

更多关于 推箱子1~15关图解 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信