题目描述
在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白色棋子,7颗黑色棋子,有两个空白地带,任何一颗黑白棋子都可以向上下左右四个方向移动到相邻的空格,这叫行棋一步,黑白双方交替走棋,任意一方可以先走,如果某个时刻使得任意一种颜色的棋子形成四个一线(包括斜线),这样的状态为目标棋局。
● ○ ●
○ ● ○ ●
● ○ ● ○
○ ● ○
输入格式
从文件中读入一个4*4的初始棋局,黑棋子用B表示,白棋子用W表示,空格地带用O表示。
输出格式
用最少的步数移动到目标棋局的步数。
输入输出样例
输入 #1复制
BWBO WBWB BWBW WBWO
输出 #1复制
5
思路分析:
根据题目描述,该题是一个求最短路问题,通常我们遇到最短路问题往往使用BFS从目标点出发四个方向搜索,对于每种走法统计步数,最后筛选出最短步数。而该题我们得知需要从两个点(空白)出发进行搜索,所以BFS很难去统计状态,因为没走一步都需要从头循环一边查看当前状态是否达到目标棋盘,时间复杂度太高必定TLE。
所以只能从DFS出发,由于黑白棋是交替行走,所以我们可以模拟黑白棋的行走方式,画出树图发现,随着层数的增加,树的子节点越来越多(搜索路径越来越多),如果采用传统的DFS去搜索必然行不通,所以我们需要优化,这里便用到了迭代加深。
迭代加深就是通过限制一次DFS的深度,来保证你的搜索不会一次性搜到底导致走了很多没用的点而扩大时间复杂度,并保证了在找不到解的情况下返回,他会以的深度为限制,把当前深度能到的点都搜出来,判断是否可行,如果可行则停止向下一层的搜索;如果不可行则再走下一层。画成树图的话会发现这种搜索像是结合了BFS与DFS。
解题步骤:
题目给的棋盘有两个空白,规定棋子只能往空白处上下左右走,换个思想,不就是从空白出发跟旁边的棋子交换位置么,所以我们首先要循环找到两个空白的位置,然后从两个位置循环搜索,记录当前搜索状态处于第几层和当前层数(层数限制),每一个节点走完了则层数加一,到达层数限制则去检查一遍当前状态是否满足题意,满足则退出,不满足在下一层继续走。
另外注意该题需要还原现场,是否还原现场依据内部搜索(不还原)和外部搜索(必须还原)来判断。内部搜索是指从一个点到另一个点,点到点之间的搜索;外部搜索是指从一个状态到另一个状态,状态间转换的搜索。很明显该题搜索过程中棋盘的状态是不断转换的。
代码:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class 四子连棋 { static class InputReader { BufferedReader br; public InputReader(InputStream stream) { br = new BufferedReader(new InputStreamReader(stream)); } public int nextInt() throws IOException { int c = br.read(); while (c <= 32) { c = br.read(); } boolean negative = false; if (c == '-') { negative = true; c = br.read(); } int x = 0; while (c > 32) { x = x * 10 + c - '0'; c = br.read(); } return negative ? -x : x; } public long nextLong() throws IOException { int c = br.read(); while (c <= 32) { c = br.read(); } boolean negative = false; if (c == '-') { negative = true; c = br.read(); } long x = 0; while (c > 32) { x = x * 10 + c - '0'; c = br.read(); } return negative ? -x : x; } public String next() throws IOException { int c = br.read(); while (c <= 32) { c = br.read(); } StringBuilder sb = new StringBuilder(); while (c > 32) { sb.append((char) c); c = br.read(); } return sb.toString(); } public double nextDouble() throws IOException { return Double.parseDouble(next()); } } static final int n = 4; static char[][] g = new char[n][n]; static boolean flag = false; // 方向数组 static int[][] next = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 检查横竖对角线和反对角线四条线有没有满足四子一色 static boolean check() { for(int i = 0; i < n; i++) { // 横代表当前行的每列上的元素相等 if(g[i][0] == g[i][1] && g[i][0] == g[i][2] && g[i][0] == g[i][3]) return true; // 竖代表当前列的每行上的元素相等 if(g[0][i] == g[1][i] && g[0][i] == g[2][i] && g[0][i] == g[3][i]) return true; } // 正对角线 if(g[0][3] == g[1][2] && g[0][3] == g[2][1] && g[0][3] == g[3][0]) return true; // 反对角线 if(g[0][0] == g[1][1] && g[0][0] == g[2][2] && g[0][0] == g[3][3]) return true; return false; } static void dfs(int x1, int y1, int x2, int y2, char last, int u, int depth) { if(u == depth) { // 满足题意则添加标记 if(check()) flag = true; return; } for(int i = 0; i < 4; i++) { // 两个空白坐标下一步到达的坐标 int nx1 = x1 + next[i][0], ny1 = y1 + next[i][1]; int nx2 = x2 + next[i][0], ny2 = y2 + next[i][1]; // 下一步不能跃出棋盘 && 不能和上一步走的颜色重色 if(nx1 >= 0 && nx1 < n && ny1 >= 0 && ny1 < n && g[nx1][ny1] != last) { // 交换位置 char tmp = g[nx1][ny1]; g[x1][y1] = tmp; g[nx1][ny1] = 'O'; // 在该搜索方案下向下一层继续搜索 dfs(nx1, ny1, x2, y2, tmp, u + 1, depth); // 还原现场 g[x1][y1] = 'O'; g[nx1][ny1] = tmp; } if(nx2 >= 0 && nx2 < n && ny2 >= 0 && ny2 < n && g[nx2][ny2] != last) { char tmp = g[nx2][ny2]; g[x2][y2] = tmp; g[nx2][ny2] = 'O'; dfs(x1, y1, nx2, ny2, tmp, u + 1, depth); g[x2][y2] = 'O'; g[nx2][ny2] = tmp; } } } public static void main(String[] args) throws Exception{ InputReader in = new InputReader(System.in); for(int i = 0; i < n; i++) g[i] = in.next().toCharArray(); int x1 = -1, y1 = -1, x2 = -1, y2 = -1; // 两个空白点坐标 for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) { if(g[i][j] == 'O') { if(x1 == -1) { x1 = i; y1 = j; // 第一次找到空白,记录下第一个坐标点 } else { x2 = i; y2 = j; // 第二次找到空白 } } } int depth = 0; // 从第0层开始(一个节点还未开始走一步) while(true) { // 白棋走完黑棋走,谁先谁后没规定 dfs(x1, y1, x2, y2, 'W', 0, depth); dfs(x1, y1, x2, y2, 'B', 0, depth); // 如果当前层数满足可以找到目标期盼则退出下一层的递归 if(flag) break; depth++; // 在下一层走 } System.out.println(depth); } }