目录
我自己的数据结构和算法一向不好,就又捡起书本学习。
然后就看《数据结构与算法分析 Java语言描述(原书第3版)》一书,会做一些练习,以下就是其中之一。
字谜游戏
按照《数据结构与算法分析 Java语言描述(原书第3版)》一书所说,字谜游戏是这样的:
输入时由一些字母构成的一个二维数组以及一组单词组成。目标是找出字谜中的单词,这些单词可能是水平、垂直或沿对角线上任何方向放置的。作为例子,图1-1所示的字谜由单词this、two、fat和that组成。单词this从第一行第一列的位置即(1,1)处开始并延伸至(1,4);单词two从(1,1)dao (3,1);fat从(4,1)到(2,3);而that则从(4,4)到(1,1)。
我的第一版代码
第一版代码完全只是参考了了这个说明,实际上是有问题的。
问题就在:
- 我没有很好的理解它所说的对角线。我理解的对角线是整个表格的对角线,而不是某个字符的对角线。
- 因为没有理解好对角线问题,导致我认为二维数组的行数和列数必须是相等的。
所以,写的代码是有问题的。
先说一下第一版的算法思路:字谜板必须是二维的数组,数组的行高度等于每一行的列长度,比如数组是4*4的二维数组。
查找逐行正向查找此行是否出现这个完整单词、逐行反向查找此行是否出现这个完整单词、逐列正向查找此行是否出现这个完整单词、逐列反向查找此行是否出现这个完整单词、对角线正向查找此行是否出现这个完整单词、对角线反向查找此行是否出现这个完整单词。
package com.luoch.study.DataStructAndAlgorithmAnalysis.puzzleboard;
/**
* 字谜板
* 字谜板必须是二维的数组,数组的行高度等于每一行的列长度,比如数组是4*4的二维数组。
* 逐行正向查找、逐行反向查找、逐列正向查找、逐列反向查找、对角线正向查找、对角线反向查找。
* 不管是正向查找还是反向查找,匹配的单词的首字符必须位于数组头或者数组尾。
* @author luoch
*/
public class PuzzleBoard {
//字谜板二维数组
private char[][] board = null;
public PuzzleBoard(char[][] arrs) {
this.board = arrs;
}
//找字谜
public int[] find(String words) {
if (words == null || words.isEmpty()) {
return null;
}
char[] wc = words.toCharArray();
if (wc.length>board.length) return null;
int[] location = null;
//行扫描【行数和列数相等】
for (int i = 0; i < board.length; i++) {//行循环
//1、逐行正向查找
for (int j = 0; j < wc.length; j++) {//列循环
// char c = board[i][j];//第i行第j列
if (wc[j] != board[i][j]) {
break;
}
if (j == wc.length-1) {
location = new int[] {i,0,i,j};
return location;
}
}
//2、逐行反向查找
for (int j = board.length - 1, k = 0; j >= 0 && k < wc.length ; j--,k++) {//列循环
if (wc[k] != board[i][j]) {
break;
}
if (k == wc.length-1) {
location = new int[] {i,board.length-1,i,j};
return location;
}
}
}
//列扫描行扫描【行数和列数相等】
for (int i = 0; i < board.length; i++) {//列循环
//3、逐列正向查找
for (int j = 0; j < wc.length; j++) {//行循环
// char c = board[j][i];//第j行第i列
if (wc[j] != board[j][i]) {
break;
}
if (j == wc.length-1) {
location = new int[] {0,i,j,i};
return location;
}
}
//4、逐列反向查找
for (int j = board.length - 1, k = 0; j >= 0 && k < wc.length ; j--,k++) {//列循环
if (wc[k] != board[j][i]) {
break;
}
if (k == wc.length-1) {
location = new int[] {board.length-1,i,j,i};
return location;
}
}
}
//对角线扫描【行数和列数相等】
//5、对角线正向查找1
for (int j = 0; j < wc.length; j++) {
if (wc[j] != board[j][j]) {
break;
}
if (j == wc.length-1) {
location = new int[] {0,0,j,j};
return location;
}
}
//6、对角线反向查找1
for (int j = board.length - 1, k = 0; j >= 0 && k < wc.length ; j--,k++) {
if (wc[k] != board[j][j]) {
break;
}
if (k == wc.length-1) {
location = new int[] {board.length - 1,board.length - 1,j,j};
return location;
}
}
//7、对角线正向查找2
for (int j=board.length - 1, k = 0; j>=0 && k < wc.length; j--, k++) {
if (wc[k] != board[j][k]) {
break;
}
if (k == wc.length-1) {
location = new int[] {board.length - 1,0,j,k};
return location;
}
}
//8、对角线反向查找2
for (int j=0, k = board.length - 1; j < wc.length && k >=0; j++, k--) {
if (wc[j] != board[j][k]) {
break;
}
if (j == wc.length-1) {
location = new int[] {0,board.length - 1,j,k};
return location;
}
}
return location;
}
public static void printResult(String words, int[] loacation) {
if (loacation == null)
System.out.printf("can not find the words:%s\n", words);
else
System.out.printf("start:(%d,%d), end:(%d,%d)\n", loacation[0]+1,
loacation[1]+1, loacation[2]+1, loacation[3]+1);
}
public static void test1() {
char[][] arrs = new char[][] {{'t','h','i','s'},{'w','a','t','s'},{'o','a','h','g'},{'f','g','d','t'}};
PuzzleBoard board = new PuzzleBoard(arrs);
String words = "this";
int[] loacation = board.find(words);
printResult(words, loacation);
words = "oa";
loacation = board.find(words);
printResult(words, loacation);
words = "fgdt";
loacation = board.find(words);
printResult(words, loacation);
words = "fgdtff";
loacation = board.find(words);
printResult(words, loacation);
words = "si";
loacation = board.find(words);
printResult(words, loacation);
}
/**
* @param args
*/
public static void main(String[] args) {
test1();
}
}
我的第一版代码的结果:发现自己理解有问题
写完第一版代码后,我并没有发现我自己的理解有问题。不过,我打开了某位网友的文章:https://blog.csdn.net/weixin_42675298/article/details/105578768 。我发现它的代码和我的很不一样,不仅简短,而且起初我居然没看懂他为什么这么写,因为一些细节不明白。比如:1、第三个for的8次循环时干什么的?2、还有那个while循环。
我当时还是原谅了自己的菜。运行了他的程序,完全运行正确。“难道是对角线”理解的不对?”,我这样怀疑。
于是我运行这位网友的代码时,特意输入以字符对角线连成的单词,没想他程序找到了单词位置。
那看来的确是我想叉了:
- 书中的二维数组并非行数与列数相等。
- 对角线并非是行数与列数相等的二维矩阵的对角线,而是数组汇总某个字符的对角线。
我的第二版代码
第一版的算法思路有问题,那么之前的查找算法就不能用了。那就重新设计算法,以下第二版的算法:
既然是要按照字符对角线来查找,那么在循环遍历二维数组时,对于每一个目标字符,实际上就有8个方向可以查找:左、右、上、下、左上、左下、右上、右下。程序开始时取得单词的首字符,开始遍历二维数组,找到第一个与这个首字符相匹配的目标字符。找到目标字符后,按照这8个方向延伸出去,不断取的下一个目标字符与单词中的下一个字符匹配。(我现在开始有点理解那位网友为什么要写那个8次循环的for了,难道就是这样的算法?)
于是我又看了他的代码,虽然某些细节逻辑不是很懂。
看别人的代码如果看不懂,那就不如按照自己想的逻辑来写,只要确保自己的思路是对的,那便没问题。于是我按照我自己的思路又写了一个2.0版本的代码。如下:
package com.luoch.study.DataStructAndAlgorithmAnalysis.puzzleboard;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 字谜板2.0
* 字谜板必须是二维的数组,2.0不再要求是行数和列数相等。在字谜板1.0中的算法是:
* 逐行正向查找、逐行反向查找、逐列正向查找、逐列反向查找、对角线正向查找、
* 对角线反向查找。在2.0中废弃了1.0的算法,2.0的算法为:从第0行第0列开始遍历
* 二维数组,遇到与单词首字母相等的字符时,则要向这个字符所在矩阵的8个方向(左、
* 右、上、下、左上、左上、右上、右下)逐个方向遍历,判断下一个字符是否匹配。
* 如某一方向的字符匹配,则在此方向继续匹配,直到匹配结束(整个单词完全匹配
* 或单词未匹配完但遇到了不匹配的字符);否则回到与单词首字符匹配的字符位置,
* 接着检查下一个字符是否与单词首字符匹配。如此重复上述过程,直到这个二维数组
* 遍历完。
* 如下的二维数组,要在其中查找abc这个单词:
* xxxxxxxxxxxxxxxx
* xxxxcxcxcxxxxxxx
* xxxxxbbbxxxxxxxx
* xxxxcbabcxxxxxxx
* xxxxxbbbxxxxxxxx
* xxxxcxcxcxxxxxxx
* xxxxxxxxxxxxxxxx
* xxxxxxxxxxxxxxxx
* 那么,就先遍历这个数组,直到在第4行第7列遇到了字符'a'。接下来,就是如之前
* 的描述,先检查本行的前一列的字符是否与单词中的'b'匹配,接着再检查再前面的
* 字符是否与'c'匹配。显然,这个找到了匹配的结果,将结果记下。回到字符'a',接着
* 检查本行的后一列的字符是否与'b'匹配。8个方向都检查完毕后,接着遍历数组,看
* 下一字符是否与'a'匹配,重复之前的操作。
* 参照了https://blog.csdn.net/weixin_42675298/article/details/105578768
* 的程序,但源程序的结构我能看懂,但一些细节我看不懂,于是我按照我自己的思路
* 来写这个程序。
* @author luoch
*/
public class PuzzleBoard2 {
//字谜板二维数组
private char[][] board = null;
/**
* 存放查找结果的map,key是一个单词,值是这个单词出现
* 位置的list。每个list元素存放一个int数组,这个int数组
* 存放key这个单词对应的字符出现的开始和结束坐标,所以每
* 个int数组的长度都是4。
*/
private Map<String, List<int[]>> result= new HashMap<String, List<int[]>>();
public PuzzleBoard2(char[][] arrs) {
this.board = arrs;
}
/**
* 找字谜
* @param words 要找的单词
* @return 如果为true,则说明在字谜板中找到了单词;否则,未找到。
*/
public boolean find(String words) {
if (words == null || words.isEmpty()) {
return false;
}
boolean get = false;
//单词转数组
char[] w = words.toCharArray();
/*单词字符数组长度*/
int wlength = w.length;
/*单词位置,长度为4:[0-头字符所在行][1-头字符所在列][2-尾字符所在行][3-尾字符所在列]*/
int[] location = null;
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
//单词首字符与目标字符相等
if (board[i][j] == w[0]) {
//0、单词长度为1时,记录,接着遍历数组。
if (wlength == 1) {
location = new int[] {i,j,i,j};
record(words, location);
get |= true;
continue;
}
//1、往左遍历查找
/*单词字符索引*/
int idx = 0;
location = new int[] {i,j,0,0};
/*临时变量,目标字符列索引*/
int temp_y = j;
//【在本行中,目标字符的前面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y > 0 && idx < wlength-1) {
--temp_y;
++idx;
//出现不匹配,直接跳出
if (board[i][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[i][temp_y] == w[idx] && idx == wlength-1) {
location[2] = i;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
//2、往右遍历查找
idx = 0;
location = new int[] {i,j,0,0};
temp_y = j;
//【在本行中,目标字符的后面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y < board[i].length-1 && idx < wlength-1) {
++temp_y;
++idx;
//出现不匹配,直接跳出
if (board[i][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[i][temp_y] == w[idx] && idx == wlength-1) {
location[2] = i;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
//3、往上遍历查找
idx = 0;
location = new int[] {i,j,0,0};
/*临时变量,目标字符行索引*/
int temp_x = i;
//【在本列中,目标字符的上面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_x > 0 && idx < wlength-1) {
--temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][j] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][j] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = j;
record(words, location);
get = true;
break;
}
}
//4、往下遍历查找
idx = 0;
location = new int[] {i,j,0,0};
temp_x = i;
//【在本列中,目标字符的下面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_x < board.length-1 && idx < wlength-1) {
++temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][j] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][j] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = j;
record(words, location);
get = true;
break;
}
}
//5、往左上遍历查找
idx = 0;
location = new int[] {i,j,0,0};
temp_x = i;
temp_y = j;
//【在本列中,目标字符的左上面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y > 0 && temp_x > 0 && idx < wlength-1) {
--temp_y;
--temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][temp_y] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
//6、往左下遍历查找
idx = 0;
location = new int[] {i,j,0,0};
temp_x = i;
temp_y = j;
//【在本列中,目标字符的左下面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y > 0 && temp_x < board.length-1 && idx < wlength-1) {
--temp_y;
++temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][temp_y] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
//7、往右上遍历查找
idx = 0;
location = new int[] {i,j,0,0};
temp_x = i;
temp_y = j;
//【在本列中,目标字符的右上面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y < board[i].length-1 && temp_x > 0 && idx < wlength-1) {
++temp_y;
--temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][temp_y] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
//8、往右下遍历查找
idx = 0;
location = new int[] {i,j,0,0};
temp_x = i;
temp_y = j;
//【在本列中,目标字符的右下面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y < board[i].length-1 && temp_x < board.length-1 && idx < wlength-1) {
++temp_y;
++temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][temp_y] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
}
}
}
return get;
}
/**
* @param args
*/
public static void main(String[] args) {
// test1();
// test2();
char[][] arrs = new char[][] {{'t','h','i','s'},{'w','a','t','s'},{'o','a','h','g'},{'f','g','d','t'}};
String[] words = new String[] {"this","oa","a", "h", "that","two","fat"};
test3(arrs, words);
}
private void printResult() {
if (!result.isEmpty()) {
Set<String> set = result.keySet();
Iterator<String> iter = set.iterator();
while(iter.hasNext()) {
String key = iter.next();
List<int[]> list = result.get(key);
for (int[] location : list) {
System.out.printf("%s start:(%d,%d), end:(%d,%d)\n", key,location[0]+1,
location[1]+1, location[2]+1, location[3]+1);
}
}
}
}
private void printResult(String words) {
if (!result.isEmpty()) {
List<int[]> list = result.get(words);
for (int[] location : list) {
System.out.printf("%s start:(%d,%d), end:(%d,%d)\n", words, location[0]+1,
location[1]+1, location[2]+1, location[3]+1);
}
}
}
private void record(String words, int[] location) {
if (result.containsKey(words)) {
List<int[]> value = result.get(words);
if (!lookup(value, location)) {
value.add(location);
}
} else {
List<int[]> value = new ArrayList<>();
value.add(location);
result.put(words, value);
}
}
private boolean lookup(List<int[]> list, int[] a) {
for (int i = 0; i < list.size(); i++) {
int[] e = list.get(i);
if (e[0] == a[0]
&& e[1] == a[1]
&& e[2] == a[2]
&& e[3] == a[3]) {
return true;
}
}
return false;
}
private static void test1() {
char[][] arrs = new char[][] {{'t','h','i','s'},{'w','a','t','s'},{'o','a','h','g'},{'f','g','d','t'}};
PuzzleBoard2 board = new PuzzleBoard2(arrs);
board.find("this");
board.find("fgdt");
boolean flag = board.find("fgdtff");
board.find("si");
board.find("ah");
board.find("ts");
board.find("dhti");
board.find("fo");
board.find("ha");
board.find("ag");
board.find("two");
board.find("twof");
board.find("tdgf");
board.find("o");
board.find("ao");
board.find("iao");
board.find("staf");
board.find("hw");
board.find("gd");
board.find("ha");
board.find("that");
board.find("at");
board.find("th");
board.find("h");
board.find("a");
board.find("a");
board.find("oa");
board.find("ghao");
board.find("abc");
board.find("haag");
board.find("two");
board.find("dhti");
board.find("tah");
board.find("tha");
board.find("fat");
board.find("staf");
board.find("gh");
board.find("ghs");
board.find("taht");
board.printResult();
}
private static void test2() {
char[][] arrs = new char[][] {{'t','h','i','s'},{'w','a','t','s'},{'o','a','h','g'},{'f','g','d','t'}};
PuzzleBoard2 board = new PuzzleBoard2(arrs);
String words = "this";
board.find(words);
boolean flag = board.find("oa");
if (flag) {
board.printResult("oa");
}
board.printResult();
}
private static void test3(char[][] arrs, String[] words) {
PuzzleBoard2 board = new PuzzleBoard2(arrs);
for (int i = 0; i < words.length; i++) {
String word = words[i];
board.find(word);
}
board.printResult();
}
}
“居然写了这么长的代码,还是技不如人呐。”别人几十行就搞定的事情我写了这么多。但这个代码的确是满足要求的,只是方find方法长了一点。代码多了一代。
我的第三版代码
第三版代码,我没有对算法逻辑做修改,只是将find方法做了拆分。如下:
package com.luoch.study.DataStructAndAlgorithmAnalysis.puzzleboard;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 字谜板2.0
* 字谜板必须是二维的数组,2.0不再要求是行数和列数相等。在字谜板1.0中的算法是:
* 逐行正向查找、逐行反向查找、逐列正向查找、逐列反向查找、对角线正向查找、
* 对角线反向查找。在2.0中废弃了1.0的算法,2.0的算法为:从第0行第0列开始遍历
* 二维数组,遇到与单词首字母相等的字符时,则要向这个字符所在矩阵的8个方向(左、
* 右、上、下、左上、左上、右上、右下)逐个方向遍历,判断下一个字符是否匹配。
* 如某一方向的字符匹配,则在此方向继续匹配,直到匹配结束(整个单词完全匹配
* 或单词未匹配完但遇到了不匹配的字符);否则回到与单词首字符匹配的字符位置,
* 接着检查下一个字符是否与单词首字符匹配。如此重复上述过程,直到这个二维数组
* 遍历完。
* 如下的二维数组,要在其中查找abc这个单词:
* xxxxxxxxxxxxxxxx
* xxxxcxcxcxxxxxxx
* xxxxxbbbxxxxxxxx
* xxxxcbabcxxxxxxx
* xxxxxbbbxxxxxxxx
* xxxxcxcxcxxxxxxx
* xxxxxxxxxxxxxxxx
* xxxxxxxxxxxxxxxx
* 那么,就先遍历这个数组,直到在第4行第7列遇到了字符'a'。接下来,就是如之前
* 的描述,先检查本行的前一列的字符是否与单词中的'b'匹配,接着再检查再前面的
* 字符是否与'c'匹配。显然,这个找到了匹配的结果,将结果记下。回到字符'a',接着
* 检查本行的后一列的字符是否与'b'匹配。8个方向都检查完毕后,接着遍历数组,看
* 下一字符是否与'a'匹配,重复之前的操作。
* 参照了https://blog.csdn.net/weixin_42675298/article/details/105578768
* 的程序,但源程序的结构我能看懂,但一些细节我看不懂,于是我按照我自己的思路
* 来写这个程序。
* @author luoch
*/
public class PuzzleBoard3 {
//字谜板二维数组
private char[][] board = null;
/**
* 存放查找结果的map,key是一个单词,值是这个单词出现
* 位置的list。每个list元素存放一个int数组,这个int数组
* 存放key这个单词对应的字符出现的开始和结束坐标,所以每
* 个int数组的长度都是4。
*/
private Map<String, List<int[]>> result= new HashMap<String, List<int[]>>();
public PuzzleBoard3(char[][] arrs) {
this.board = arrs;
}
//找字谜
public boolean find(String words) {
if (words == null || words.isEmpty()) {
return false;
}
boolean get = false;
//单词转数组
char[] w = words.toCharArray();
/*单词字符数组长度*/
int wlength = w.length;
/*单词位置,长度为4:[0-头字符所在行][1-头字符所在列][2-尾字符所在行][3-尾字符所在列]*/
int[] location = null;
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
//单词首字符与目标字符相等
if (board[i][j] == w[0]) {
//0、单词长度为1时,记录,接着遍历数组。
if (wlength == 1) {
location = new int[] {i,j,i,j};
record(words, location);
get |= true;
continue;
}
//1、往左遍历查找
get |= lookupLeft(i, j, wlength, words, w);
//2、往右遍历查找
get |= lookupRight(i, j, wlength, words, w);
//3、往上遍历查找
get |= lookupUp(i, j, wlength, words, w);
//4、往下遍历查找
get |= lookupDown(i, j, wlength, words, w);
//5、往左上遍历查找
get |= lookupLeftUp(i, j, wlength, words, w);
//6、往左下遍历查找
get |= lookupLeftDown(i, j, wlength, words, w);
//7、往右上遍历查找
get |= lookupRightUp(i, j, wlength, words, w);
//8、往右下遍历查找
get |= lookupRightDown(i, j, wlength, words, w);
}
}
}
return get;
}
/**
* @param args
*/
public static void main(String[] args) {
// test1();
// test2();
char[][] arrs = new char[][] {{'t','h','i','s'},{'w','a','t','s'},{'o','a','h','g'},{'f','g','d','t'}};
String[] words = new String[] {"this","oa","a", "h", "that","two","fat"};
test3(arrs, words);
}
/**
* 在当前行往左遍历查找
* @param i 目标字符所在当前行索引
* @param j 目标字符所在当前列索引
* @param wlength 单词长度
* @param words 单词
* @param w 单词数组
* @return 是否找到的标识,找到为true,未找到为false
*/
private boolean lookupLeft(int i, int j, int wlength, String words, char[] w) {
/*单词字符索引*/
int idx = 0;
int[] location = new int[] {i,j,0,0};
/*临时变量,目标字符列索引*/
int temp_y = j;
boolean get = false;
//【在本行中,目标字符的前面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y > 0 && idx < wlength-1) {
--temp_y;
++idx;
//出现不匹配,直接跳出
if (board[i][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[i][temp_y] == w[idx] && idx == wlength-1) {
location[2] = i;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
return get;
}
/**
* 在当前行往右遍历查找
* @param i 目标字符所在当前行索引
* @param j 目标字符所在当前列索引
* @param wlength 单词长度
* @param words 单词
* @param w 单词数组
* @return 是否找到的标识,找到为true,未找到为false
*/
private boolean lookupRight(int i, int j, int wlength, String words, char[] w) {
/*单词字符索引*/
int idx = 0;
int[] location = new int[] {i,j,0,0};
/*临时变量,目标字符列索引*/
int temp_y = j;
boolean get = false;
//【在本行中,目标字符的后面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y < board[i].length-1 && idx < wlength-1) {
++temp_y;
++idx;
//出现不匹配,直接跳出
if (board[i][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[i][temp_y] == w[idx] && idx == wlength-1) {
location[2] = i;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
return get;
}
/**
* 在当前列往上遍历查找
* @param i 目标字符所在当前行索引
* @param j 目标字符所在当前列索引
* @param wlength 单词长度
* @param words 单词
* @param w 单词数组
* @return 是否找到的标识,找到为true,未找到为false
*/
private boolean lookupUp(int i, int j, int wlength, String words, char[] w) {
int idx = 0;
int[] location = new int[] {i,j,0,0};
/*临时变量,目标字符行索引*/
int temp_x = i;
boolean get = false;
//【在本列中,目标字符的上面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_x > 0 && idx < wlength-1) {
--temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][j] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][j] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = j;
record(words, location);
get = true;
break;
}
}
return get;
}
/**
* 在当前列往下遍历查找
* @param i 目标字符所在当前行索引
* @param j 目标字符所在当前列索引
* @param wlength 单词长度
* @param words 单词
* @param w 单词数组
* @return 是否找到的标识,找到为true,未找到为false
*/
private boolean lookupDown(int i, int j, int wlength, String words, char[] w) {
int idx = 0;
int[] location = new int[] {i,j,0,0};
int temp_x = i;
boolean get = false;
//【在本列中,目标字符的下面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_x < board.length-1 && idx < wlength-1) {
++temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][j] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][j] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = j;
record(words, location);
get = true;
break;
}
}
return get;
}
/**
* 在当前列往左上遍历查找
* @param i 目标字符所在当前行索引
* @param j 目标字符所在当前列索引
* @param wlength 单词长度
* @param words 单词
* @param w 单词数组
* @return 是否找到的标识,找到为true,未找到为false
*/
private boolean lookupLeftUp(int i, int j, int wlength, String words, char[] w) {
int idx = 0;
int[] location = new int[] {i,j,0,0};
int temp_x = i;
int temp_y = j;
boolean get = false;
//【在本列中,目标字符的左上面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y > 0 && temp_x > 0 && idx < wlength-1) {
--temp_y;
--temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][temp_y] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
return get;
}
/**
* 在当前列往左下遍历查找
* @param i 目标字符所在当前行索引
* @param j 目标字符所在当前列索引
* @param wlength 单词长度
* @param words 单词
* @param w 单词数组
* @return 是否找到的标识,找到为true,未找到为false
*/
private boolean lookupLeftDown(int i, int j, int wlength, String words, char[] w) {
int idx = 0;
int[] location = new int[] {i,j,0,0};
int temp_x = i;
int temp_y = j;
boolean get = false;
//【在本列中,目标字符的左下面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y > 0 && temp_x < board.length-1 && idx < wlength-1) {
--temp_y;
++temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][temp_y] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
return get;
}
/**
* 在当前列往右上遍历查找
* @param i 目标字符所在当前行索引
* @param j 目标字符所在当前列索引
* @param wlength 单词长度
* @param words 单词
* @param w 单词数组
* @return 是否找到的标识,找到为true,未找到为false
*/
private boolean lookupRightUp(int i, int j, int wlength, String words, char[] w) {
int idx = 0;
int[] location = new int[] {i,j,0,0};
int temp_x = i;
int temp_y = j;
boolean get = false;
//【在本列中,目标字符的右上面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y < board[i].length-1 && temp_x > 0 && idx < wlength-1) {
++temp_y;
--temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][temp_y] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
return get;
}
/**
* 在当前列往右下遍历查找
* @param i 目标字符所在当前行索引
* @param j 目标字符所在当前列索引
* @param wlength 单词长度
* @param words 单词
* @param w 单词数组
* @return 是否找到的标识,找到为true,未找到为false
*/
private boolean lookupRightDown(int i, int j, int wlength, String words, char[] w) {
int idx = 0;
int[] location = new int[] {i,j,0,0};
int temp_x = i;
int temp_y = j;
boolean get = false;
//【在本列中,目标字符的右下面还有字符】 并且 【单词中当前字符不是单词的结束字符】
while (temp_y < board[i].length-1 && temp_x < board.length-1 && idx < wlength-1) {
++temp_y;
++temp_x;
++idx;
//出现不匹配,直接跳出
if (board[temp_x][temp_y] != w[idx]) {
break;
}
//单词的最后一个字符
if (board[temp_x][temp_y] == w[idx] && idx == wlength-1) {
location[2] = temp_x;
location[3] = temp_y;
record(words, location);
get = true;
break;
}
}
return get;
}
private void printResult() {
if (!result.isEmpty()) {
Set<String> set = result.keySet();
Iterator<String> iter = set.iterator();
while(iter.hasNext()) {
String key = iter.next();
List<int[]> list = result.get(key);
for (int[] location : list) {
System.out.printf("%s start:(%d,%d), end:(%d,%d)\n", key,location[0]+1,
location[1]+1, location[2]+1, location[3]+1);
}
}
}
}
private void printResult(String words) {
if (!result.isEmpty()) {
List<int[]> list = result.get(words);
for (int[] location : list) {
System.out.printf("%s start:(%d,%d), end:(%d,%d)\n", words, location[0]+1,
location[1]+1, location[2]+1, location[3]+1);
}
}
}
private void record(String words, int[] location) {
if (result.containsKey(words)) {
List<int[]> value = result.get(words);
if (!lookup(value, location)) {
value.add(location);
}
} else {
List<int[]> value = new ArrayList<>();
value.add(location);
result.put(words, value);
}
}
private boolean lookup(List<int[]> list, int[] a) {
for (int i = 0; i < list.size(); i++) {
int[] e = list.get(i);
if (e[0] == a[0]
&& e[1] == a[1]
&& e[2] == a[2]
&& e[3] == a[3]) {
return true;
}
}
return false;
}
private static void test1() {
char[][] arrs = new char[][] {{'t','h','i','s'},{'w','a','t','s'},{'o','a','h','g'},{'f','g','d','t'}};
PuzzleBoard3 board = new PuzzleBoard3(arrs);
board.find("this");
board.find("fgdt");
boolean flag = board.find("fgdtff");
board.find("si");
board.find("ah");
board.find("ts");
board.find("dhti");
board.find("fo");
board.find("ha");
board.find("ag");
board.find("two");
board.find("twof");
board.find("tdgf");
board.find("o");
board.find("ao");
board.find("iao");
board.find("staf");
board.find("hw");
board.find("gd");
board.find("ha");
board.find("that");
board.find("at");
board.find("th");
board.find("h");
board.find("a");
board.find("a");
board.find("oa");
board.find("ghao");
board.find("abc");
board.find("haag");
board.find("two");
board.find("dhti");
board.find("tah");
board.find("tha");
board.find("fat");
board.find("staf");
board.find("gh");
board.find("ghs");
board.find("taht");
board.printResult();
}
private static void test2() {
char[][] arrs = new char[][] {{'t','h','i','s'},{'w','a','t','s'},{'o','a','h','g'},{'f','g','d','t'}};
PuzzleBoard3 board = new PuzzleBoard3(arrs);
String words = "this";
board.find(words);
boolean flag = board.find("oa");
if (flag) {
board.printResult("oa");
}
board.printResult();
}
private static void test3(char[][] arrs, String[] words) {
PuzzleBoard3 board = new PuzzleBoard3(arrs);
for (int i = 0; i < words.length; i++) {
String word = words[i];
board.find(word);
}
board.printResult();
}
}
重看网友的代码
在改完第三版代码,我又重回去看了网友的代码,再结合自己的思路,终于明白之前不理解的地方了。以下是他的代码。
//来自https://blog.csdn.net/weixin_42675298/article/details/105578768
class Riddle {
public static final String[] WORDS = {"this", "two", "fat", "that"};
public static final char[][] CONST = {{'t', 'h', 'i', 's'}, {'w', 'a', 't', 's'}, {'o', 'a', 'h', 'g'}, {'f', 'g', 'd', 't'}};
public static void main(String[] args) {
findWords(CONST, WORDS);
}
public static void findWords(char[][] arr, String[] words) {
// 记录首字母,不重复
StringBuilder initials = new StringBuilder();
for (String word : words) {
String initial = word.substring(0, 1);
if (!initials.toString().contains(initial)) {
initials.append(initial);
}
}
// 遍历每个元素是否是首字母
for (int x = 0; x < arr.length; x++) {
for (int y = 0; y < arr[x].length; y++) {
if (initials.toString().contains(String.valueOf(arr[x][y]))) {
for (int p = 1; p <= 8; p++) {
String tem = "";
int xEnd = x, yEnd = y;
while (xyAva(arr, xEnd, yEnd)) {
tem += arr[xEnd][yEnd];
printWord(tem, x, y, xEnd, yEnd);
if (p == 1) {
xEnd--;
}
if (p == 2) {
xEnd++;
}
if (p == 3) {
yEnd++;
}
if (p == 4) {
yEnd--;
}
if (p == 5) {
xEnd--;
yEnd++;
}
if (p == 6) {
xEnd++;
yEnd--;
}
if (p == 7) {
xEnd++;
yEnd++;
}
if (p == 8) {
xEnd--;
yEnd--;
}
}
}
}
}
}
}
public static boolean xyAva(char[][] arr, int x, int y) {
if (0 <= x && x < arr.length) {
return 0 <= y && y < arr[x].length;
}
return false;
}
/**
* 判断是否是给定的单词并打印
*/
public static void printWord(String word, int x, int y, int xEnd, int yEnd) {
for (String s : WORDS) {
if (word.equals(s)) {
System.out.println("单词:"+ s + "\t向量:" + "(" + (x + 1) + "," + (y + 1) + ")->(" + (xEnd + 1) + "," + (yEnd + 1) + ")");
}
}
}
}
他的实现算法基本和我想的一致。但个别地方实现有差异。
比如:
1、虽然是按照当前字符的8个方向去判断下一个目标字符是否匹配,但他的while循环就写得比我的精简。(算因为我个人原因我期初是没看懂的)
2、在它的while循环中,有两行代码:【tem += arr[xEnd][yEnd];printWord(tem, x, y, xEnd, yEnd);】这两行的代码的逻辑是:将之前数组中匹配的目标字符存放到tem变量中,然后再取数组中的下一个目标字符与之前的tem拼接。拼接后的新term,会拿来和输入的单词集合做比对,判断这个tem是否与集合中的某个单词匹配。而我的是怎么写的呢,将单词拆分成字符数组,这个字符数组将字符数组中的字符取出与二维数组中的目标字符逐个做判断是否匹配。
3、网友的二维数组的边界判断写在了xyAva(char[][] arr, int x, int y)方法中,而我的则是分情况分散在8个地方。
当然还有其他地方差异,比如我的是逐个取单词取比对,而他的这是取所有单词的首字母去比对。
总结
这个字谜的Java代码虽然写3版,但和其他网友还是有差距的,继续努力吧。