这是今天某同班同学问我的一道题目,主要涉及了DFS及回溯思想,和本人之前分享过的"八皇后问题"有异曲同工之妙。
题目描述
这里有个故事。
John是一名强壮的篮球运动员,在一次投篮练习中,用力过大将篮球扔到了篮球场隔壁的迷宫中。他着急于接下来的训练,因此直接冲进了迷宫,但是没想到篮球早已被工作人员放到了迷宫的终点。
接下来的事你应该猜到了,故事就变成了John走迷宫的故事。现在请你设计一个程序,帮John计算一下他至少要走几步才能到达迷宫的终点,拿到篮球。
迷宫用一个二维数组构建的字符矩阵来表示。字符S表示John所在的位置。字符E表示篮球所在的位置。字符#表示墙壁。字符.表示道路。John每步可以在道路上朝上下左右任意一个方向走一个单位,但不能走出迷宫边界,也不能撞到墙壁。
输入与输出要求
编写一个名为basketball的函数,调用它输入一个二维数组。二维数组第一层代表迷宫若干行,第二层含若干个字符,代表迷宫每行内的情况。字符含义如题目描述中所述,保证有且仅有一个S和E。
该函数返回John拿到篮球所需的最少步数。若John永远无法拿到篮球,则返回字符串no!。
测试样例
| Input | Output |
|---|---|
| [['.', 'S', '.', '.'],['#', '#', '#', '.'],['.', '.', 'E', '.']] | 5 |
| [['.', 'S', '.', '.'],['.', 'E', '.', '.'],['.', '.', '.', '.']] | 1 |
| [['.', 'S', '.', '.'],['#', '#', '#', '#'],['.', '.', 'E', '.']] | no! |
题解
关键代码的注释写得应该比较清楚了。
"use strict";
const directions = [{addX: 0, addY: -1}, {addX: 0, addY: 1}, {addX: -1, addY: 0}, {addX: 1, addY: 0}];
function createChecker(matrix, hasVisitedRecord) {
return (x, y) => x >=0 && y >=0 && y < matrix.length && x < matrix[0].length && matrix[y][x] !== '#' && !hasVisitedRecord.has(`${x}_${y}`);
}
function dfs(matrix, {x, y}, step, stepRecord, hasVisitedRecord, checker) {
for(let {addX, addY} of directions) { //逐次尝试四个方向
let newX = x + addX;
let newY = y + addY;
if(checker(newX, newY)) { //检测当前尝试的方向是否可以走
//如果找到终点,记录步数,不再继续往下搜索
//注意不能用step++,因为其他几个方向可能都还没尝试,走到终点后仍需退回上一格去尝试其他方向
if(matrix[newY][newX] === 'E') return stepRecord.add(step+1);
//如果此路为活路,继续向下搜索
hasVisitedRecord.add(`${newX}_${newY}`); //记录当前坐标已经走过,防止走回头路
dfs(matrix, {x: newX, y: newY}, ++step, stepRecord, hasVisitedRecord, checker); //以当前格为起点继续向下搜索
//执行到这里说明上一步所作搜索已经结束,需要进行回溯
//然后尝试剩余的还未尝试的方向(如果还剩的话)
step--;
hasVisitedRecord.delete(`${newX}_${newY}`);
}
}
}
function basketball(matrix) {
let startPoint;
let hasVisitedRecord = new Set();
let stepRecord = new Set();
//查找起点坐标
matrix.forEach((arr, y) =>
arr.forEach((str, x) =>
str === 'S' && (startPoint = {x, y}, hasVisitedRecord.add(`${x}_${y}`))
)
);
//从起点出发开始DFS搜索
dfs(matrix, startPoint, 0, stepRecord, hasVisitedRecord, createChecker(matrix, hasVisitedRecord));
//输出结果
return stepRecord.size ? Math.min(...stepRecord) : 'no!';
}