最近在刷公司的OJ算法题,发现好多算法都不会,像什么动态规划、背包算法、回溯法、线段树、稀疏存储balabala,这些算法随便一个,自己查资料看懂然后把程序编写出来,然后再归纳总结,感觉至少一天的时间。本来的打算是,赶紧把题目刷到10000分,然后再开始干活,但昨天做了一道营救公主,发现自己代码写的一团糟,虽然算法思路都看懂了,可是自己写起来特别的繁琐,深深感受到自己的基本功还得好好磨练一下,觉得不能只当成一项任务去应付了,不能急于求成,要保质保量。
题目:
500年前,Jesse是我国最卓越的剑客。他英俊潇洒,而且机智过人^_^。
突然有一天,Jesse心爱的公主被魔王困在了一个巨大的迷宫中。Jesse听说这个消息已经是两天以后了,他知道公主在迷宫中还能坚持T天,他急忙赶到迷宫,开始到处寻找公主的下落。 时间一点一点的过去,Jesse还是无法找到公主。最后当他找到公主的时候,美丽的公主已经死了。从此Jesse郁郁寡欢,茶饭不思,一年后追随公主而去了。T_T 500年后的今天,Jesse托梦给你,希望你帮他判断一下当年他是否有机会在给定的时间内找到公主。
他会为你提供迷宫的地图以及所剩的时间T。请你判断他是否能救出心爱的公主。
题目包括多组测试数据。 每组测试数据以三个整数N,M,T(0
样例输入:
m = 4, n = 4, t = 10
迷宫图如下
....
....
....
S**P
样例输出:
0
描述:
/*
* 每组测试数据包括三个整数t,n,m(00),分别代表公主能坚持的天数,迷宫的长和高。紧接着有m行,n列字符,由".","*","P","S"组成。
* 其中 "." 代表能够行走的空地。 "*" 代表墙壁,Jesse不能从此通过。
* "P" 是公主所在的位置。 "S" 是Jesse的起始位置。 每个时间段里Jesse
* 只能选择“上、下、左、右”任意一方向走一步。
* 迷宫布局(这里用二维数组实现布局) m 迷宫(数组)行数 n 迷宫(数组)列数
* T 公主能坚持的天数
* Return Value 0 可以救出公主 -1 不可以救出公主
*/
public int SSavep(char[][] visited, int t, int n, int m) {
// 这里面添加函数功能
return 0;
}
解题:
拿到这道题目的时候,感觉其实还好,不是很难,稍微思考了一下,记得之前看过的算法A*算法,http://www.blueidea.com/tech/multimedia/2005/3121_3.asp,里面算法讲解的非常详细,翻出来再看了一遍,便开始自己写代码了。
package huawei;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Demo {
// 开启节点列表
// 通过反复遍历开启列表并选择具有最低F值得方格生成
List openList = new ArrayList<>();
// 关闭列表
List closeList = new ArrayList<>();
Node startNode = null;
Node endNode = null;
int N , M;
char[][] c = null;
// 用来判断目标节点是否添加进关闭列表
boolean isEndNodeInCloseList = false;
/*
* 每组测试数据以三个整数N,M,T(00)开头,分别代表迷宫的长和高,
* 以及公主能坚持的天数。紧接着有M行,N列字符,由".","*","P","S"组成。
* 其中 "." 代表能够行走的空地。 "*" 代表墙壁,Jesse不能从此通过。
* "P" 是公主所在的位置。 "S" 是Jesse的起始位置。 每个时间段里Jesse
* 只能选择“上、下、左、右”任意一方向走一步。
* 迷宫布局(这里用二维数组实现布局) M 迷宫(数组)行数 N 迷宫(数组)列数
* T 公主能坚持的天数
* Return Value 0 可以救出公主 -1 不可以救出公主
*/
public int SSavep(char[][] visited, int t, int n, int m) {
// 存放当前操作的节点,也就是从开启列表中找到的权重最小的格子
Node currentNode = null;
N = n;
M =m;
c = visited;
// 获取开始,目标节点
for(int i=0; i= endNode.getWeight()) {
return 0;
}else {
return -1;
}
}else {
return -1;
}
}
// 在开启列表中寻找权重最小的节点
public Node searchNode(List list1, List list2) {
Node node = null;
Iterator it = list1.iterator();
while(it.hasNext()) {
Node n = it.next();
if(node == null || node.getWeight() > n.getWeight()) {
node = n;
}
}
list1.remove(node);
list2.add(node);
return node;
}
// 处理当前节点上下左右相邻的节点
void doNeighborNode(Node currentNode, char[][] visited) {
int x = currentNode.getX();
int y = currentNode.getY();
int s = currentNode.getToOriginStep() + 1;
if(x - 1 >=0 ) {
addInStartList(x-1, y, s, currentNode);
}
if(x + 1 < N) {
addInStartList(x+1, y, s, currentNode);
}
if(y - 1 >= 0) {
addInStartList(x, y-1, s, currentNode);
}
if(y + 1 < M) {
addInStartList(x, y+1, s, currentNode);
}
}
// 判断是否已经在开启列表中
boolean isInStartList(int x, int y, int s, Node currentNode) {
Iterator it = openList.iterator();
while(it.hasNext()) {
Node node = it.next();
if(node.getX() == x && node.getY() == y) {
if(node.getToOriginStep() > s) {
node.setParent(currentNode);
node.setToOriginStep(s);
node.setWeight();
}
return true;
}
}
return false;
}
// 将相邻节点进行放入开启列表的处理
void addInStartList(int x, int y, int s, Node currentNode) {
if(isInStartList(x, y, s, currentNode)) {
}else {
if('*' != c[x][y] && !isContain(closeList, x, y)) {
if(x == endNode.getX() && y == endNode.getY()) {
endNode.setToOriginStep(s);
endNode.setToTerminalStep(0);
endNode.setWeight();
openList.add(endNode);
isEndNodeInCloseList = true;
}else {
Node node = new Node(x, y);
node.setToOriginStep(s);
node.setToTerminalStep(Math.abs(x - endNode.getX()) + Math.abs(y - endNode.getY()));
node.setWeight();
node.setParent(currentNode);
openList.add(node);
}
}
}
}
boolean isContain(List list, int x, int y) {
Iterator it = list.iterator();
while(it.hasNext()) {
Node node = it.next();
if(node.getX() == x && node.getY() == y) {
return true;
}
}
return false;
}
}
package huawei;
public class Node {
private int x;
private int y;
private int weight;
private int toOriginStep;
private int toTerminalStep;
private Node parent;
public Node(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getWeight() {
return weight;
}
public void setWeight() {
this.weight = this.toOriginStep + this.toTerminalStep;
}
public int getToOriginStep() {
return toOriginStep;
}
public void setToOriginStep(int toOriginStep) {
this.toOriginStep = toOriginStep;
}
public int getToTerminalStep() {
return toTerminalStep;
}
public void setToTerminalStep(int toTerminalStep) {
this.toTerminalStep = toTerminalStep;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
}
辛辛苦苦写了一上午,自我感觉代码写的非常繁琐。大致的思路是这样:
- 把起始格添加到开启列表
- 重复一下循环:a)寻找开启列表中weight值最低的元素(weight = toOriginStep + toTerminalStep),作为currentNode; b)把这个元素从开启列表中移除,放入关闭列表中; c)按照可移动的方向,对相邻的元素进行处理:如果这个元素不可通过即为“*”,或者已经在关闭列表中,略过它;反之,如果这个元素不在开启列表中,把它添加进开启列表;反之,如果这个元素已经在开启列表中,用toOriginStep值考察是否新路径更好,toOriginStep值更低则路径更好,如果新路径toOriginStep更低,则更新toOriginStep值和该元素的父节点。d) 循环停止的条件:把目标节点加进了关闭列表,这个时候路径找到;或者,没有找到目标节点,开启列表已经空了,这个时候,路径不存在。
- 保存路径,从目标格开始,沿着每一个元素的父节点移动直到回到起始格。
写完后提交,只得了一半的分值,再仔细检查了一遍,没发觉问题在哪,如果有看过我代码的大神,还请评论指导一下。
后面在评论区看到一种非常简洁的方法,就是采用相反的思路:查看从某一个点开始,N步可以最远达到哪里。
如图:
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
S |
* |
* |
P |
初始化图如下:
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
0 |
* |
* |
P |
构造1步可以到达的地方:
. |
. |
. |
. |
. |
. |
. |
. |
1 |
. |
. |
. |
0 |
* |
* |
P |
构造2步可以到达的地方:
. |
. |
. |
. |
2 |
. |
. |
. |
1 |
2 |
. |
. |
0 |
* |
* |
P |
构造3步可以到达的地方:
3 |
. |
. |
. |
2 |
3 |
. |
. |
1 |
2 |
3 |
. |
0 |
* |
* |
P |
构造4步可以到达的地方:
3 |
4 |
. |
. |
2 |
3 |
4 |
. |
1 |
2 |
3 |
4 |
0 |
* |
* |
P |
构造5步可以到达的地方:
3 |
4 |
5 |
. |
2 |
3 |
4 |
5 |
1 |
2 |
3 |
4 |
0 |
* |
* |
5 |
构造6步可以到达的地方:
3 |
4 |
5 |
6 |
2 |
3 |
4 |
5 |
1 |
2 |
3 |
4 |
0 |
* |
* |
5 |
可以看到P的位置是5,也就是说最少5步可以到达P的位置。
代码实现:
package huawei;
public class Demo {
public int SSavep(char[][] visited, int t, int n, int m) {
int[][] map = new int[visited.length][visited[0].length];
int startx=0, starty=0, endx=0, endy=0;
for (int i = 0; i < visited.length; i++) {
for (int j = 0; j < visited[0].length; j++) {
map[i][j] = Integer.MAX_VALUE;
if ('P' == visited[i][j]) {
endx = i;
endy = j;
} else if ('S' == visited[i][j]) {
map[i][j] = 0;
startx = i;
starty = j;
}
}
}
// 从开始位置计算所有表格的值
mark(visited, map, startx, starty - 1); // 左
mark(visited, map, startx - 1, starty); // 上
mark(visited, map, startx, starty + 1); // 右
mark(visited, map, startx + 1, starty); // 下
// 获取公主位置的值
int minStep = map[endx][endy];
return minStep <= t ? 0 : -1;
}
// 根据周边的值计算自己最小的值,如果自己的值改变,则重新计算周边的值
private void mark(char[][] visited, int[][] map, int x, int y) {
if (x < 0 || x >= map.length || y < 0 || y >= map[0].length) {
return;
}
if(visited[x][y] == '*') {
return;
}
int newValue = getArroundMin(map, x, y) + 1;
if (newValue < map[x][y]) {
map[x][y] = newValue;
mark(visited, map, x, y - 1); // 左
mark(visited, map, x - 1, y); // 上
mark(visited, map, x, y + 1); // 右
mark(visited, map, x + 1, y); // 下
}
}
// 计算周围最小的值
private int getArroundMin(int[][] map, int x, int y) {
int min = Integer.MAX_VALUE;
// 左
if (y > 0) {
min = Math.min(min, map[x][y - 1]);
}
// 上
if (x > 0) {
min = Math.min(min, map[x - 1][y]);
}
// 右
if (y < map[0].length - 1) {
min = Math.min(min, map[x][y + 1]);
}
// 下
if (x < map.length - 1) {
min = Math.min(min, map[x + 1][y]);
}
return min;
}
}
代码很简洁,采用了递归算法,自己写代码的时候,很少会使用递归,得好好学习一下这种思路。