Word Maze 网络小游戏

Word Maze网络小游戏及华为机试题
Word Maze是一个网络小游戏,玩家需要按照给定单词顺序吃掉字母,类似华为机试题目。玩家在n×m的字母迷宫中,寻找并连通形成目标单词的食物路径。博主分享了游戏的样例输出和代码。

Word Maze 网络小游戏

Word Maze 是一个网络小游戏,你需要找到以字母标注的食物,但要求以给定单词字母的顺序吃掉。如上图,假设给定单词if,

你必须先吃掉i然后才能吃掉f。但现在你的任务可没有这么简单,你现在处于一个迷宫Maze(n×m的矩阵)当中,里面到处都是以字母标注的食物,

但你只能吃掉能连成给定单词W的食物。

如下图,指定W为“SOLO”,则在地图中红色标注了单词“SOLO”。 

注意区分英文字母大小写,你只能上下左右行走。
输入:
输入第一行包含两个整数n、m(0<n, m<21)分别表示n行m列的矩阵,第二行是长度不超过100的单词W,从第3行到底n+3行是
只包含大小写英文字母的长度为m的字符串。
输出:
如果能在地图中连成给定的单词,则输出“YES”,否则输出“NO”。注意:每个字母只能用一次。
样例输入:
5 5
SOLO
CPUCY
EKLQH
CRSOL
EKLQO
PGRBC

样例输出: YES


这是一道华为机试题,博

<think>好的,我现在需要帮用户解决用C语言实现WordMaze游戏的问题,判断矩阵中是否存在按照给定单词顺序连接的路径。用户提到了DFS或BFS算法,我需要先理清这两个算法的区别以及如何应用到这个问题上。 首先,问题要求的是在矩阵中寻找连续的路径,每个字符必须按顺序匹配给定单词,并且路径相邻可以是上下左右,不能重复使用同一个位置的字符。这应该是一个典型的路径搜索问题,通常DFS比较常用,因为需要探索所有可能的路径直到找到正确的或者全部排除。不过BFS也可以,但可能需要更多的内存来存储状态。 接下来,我需要确定数据结构。矩阵可以用二维数组表示,单词是字符串。需要记录访问过的位置,防止重复访问。对于DFS,递归实现可能更直观,但需要注意栈深度的问题,如果矩阵很大或者单词很长,可能会有栈溢出的风险。不过一般情况下,题目可能假设矩阵规模不会太大,所以递归应该可行。然后,考虑算法的步骤。对于每个起点(即矩阵中与单词一个字符相同的位置),启动DFS或BFS搜索。在每一步,检查当前位置的字符是否匹配单词的当前字符,如果匹配且未访问过,则标记为已访问,并向四个方向递归搜索下一个字符。如果某个方向返回成功,则整体成功;否则回溯,取消标记,继续其他方向。这里需要注意剪枝,一旦发现当前字符不匹配,立即停止该路径的搜索。此外,如果单词长度为0或者已经匹配完所有字符,应该返回成功。接下来,如何处理边界条件?比如矩阵的边界位置,四个方向中的某些可能越界,需要判断坐标是否在有效范围内。还有,当单词长度为0时如何处理?不过题目应该保证单词非空,但代码中还是需要处理。 然后,关于具体的实现细节。比如,访问标记数组应该是一个二维数组,大小与矩阵相同,每次进入递归时标记当前位置,退出时取消标记,以便其他路径可以重新使用该位置。这需要动态分配或者在栈上创建,不过C语言中动态分配可能更灵活,特别是当矩阵大小不固定时。 对于代码结构,主函数可能负责遍历所有可能的起点,调用DFS函数。DFS函数需要参数包括当前坐标、当前匹配的位置、矩阵、单词、访问数组等。如果找到路径,立即返回true,否则继续搜索。 测试用例方面,需要考虑存在路径、不存在路径、多个起点、路径需要回溯等情况。例如,矩阵中有多个起点,但只有其中一个能形成有效路径,或者路径需要绕行但被访问标记阻止,导致需要正确回溯。现在比较DFS和BFS的选择。DFS实现起来递归比较方便,但可能在某些情况下效率不如BFS,尤其是当路径较长且分支较多时,DFS可能会深入无效路径太久。而BFS可以逐层扩展,找到最短路径,但在这个问题中并不需要最短路径,只需要存在性,所以两者都可以。不过用户可能更倾向于DFS,因为代码结构更简洁。 另外,关于性能优化,比如提前终止搜索,一旦找到一条路径就立即返回,不需要继续搜索其他可能性。这在代码中应该通过返回值处理,一旦递归调用返回true,就层层返回,不再继续其他方向。 然后,编写代码的大致步骤:1.输入矩阵和单词,检查单词是否为空,矩阵是否有效。 2.创建访问标记数组,初始化为false。 3.遍历矩阵的每个位置,如果该位置的字符等于单词的第一个字符,则启动DFS搜索。 4. DFS函数中,检查当前字符是否匹配,如果已经到单词末尾,返回true。5.否则,标记当前位置为已访问,向四个方向探索,递归调用DFS。 6.如果任一方向返回true,则整体返回true。 7.回溯时,取消标记当前位置。8.如果所有方向都失败,返回false。需要注意的细节:四个方向的坐标变化,可以用方向数组dx和dy,比如{-1,0,1,0}和{0,1,0,-1}对应上下左右。在循环中遍历这四个方向。 另外,在C语言中,二维数组作为参数传递时需要注意维度,或者使用一维数组模拟二维,或者动态分配。这里假设矩阵是动态分配的,或者使用全局变量,但为了函数通用性,可能需要将矩阵的行数和列数作为参数传递。例如,DFS函数的参数可能包括矩阵的行数rows,列数cols,当前坐标i和j,当前单词的位置index,矩阵本身,单词指针,以及访问数组。 此外,内存管理方面,访问数组需要在每次启动DFS时初始化为false吗?或者每次遍历起点时重新初始化?不,应该在每次从新的起点开始搜索时,重新初始化访问数组,或者在DFS内部处理标记,因为不同的起点之间是独立的搜索过程。例如,当从(i,j)开始搜索时,访问数组只记录该次搜索中已访问的位置,所以每次新的起点都需要一个新的访问数组。或者在每次启动DFS前,将访问数组全部置为false,但这样效率不高。更好的方法是在每次使用一个起点时,创建新的访问数组,或者在DFS函数内部处理,每次进入递归时标记,退出时取消,这样不同起点的搜索不会互相干扰。例如,当从起点(0,0)开始搜索时,访问数组在递归过程中被标记,当递归返回后,下一个起点(0,1)的搜索会使用新的访问数组,或者在每次起点开始时重新初始化访问数组。这可能需要在主循环中,每次为每个起点创建一个新的访问数组。例如:for (int i=0;i <rows;i++){for (int j=0;j <cols;j++){if (matrix[i][j] ==word[0]){bool **visited =create_visited(rows,cols);if(dfs(...)) {free_visited(visited);returntrue;}free_visited(visited);}} }这样每次尝试一个起点时,都创建新的访问数组,避免不同起点之间的搜索互相影响。但这样会增加内存的使用,特别是当矩阵很大时。有没有更好的方法?比如,在DFS过程中,每次进入时标记,退出时取消,这样同一访问数组可以被不同的起点复用,因为当从一个起点开始搜索结束后,访问数组会被完全回溯,所有标记都被取消。但这样是否可行?例如,当从起点A开始搜索,访问数组被标记,搜索结束后,所有标记被取消,那么下一个起点B的搜索可以使用同一个访问数组。这样可能节省内存,但需要确保每次搜索结束后,访问数组被完全重置。这可以通过正确的回溯来实现,即在DFS函数中,每次递归调用后恢复访问标记。因此,访问数组可以在主函数中只创建一次,然后在每次起点搜索时重复使用,但每次搜索前必须确保所有位置都是未访问的。或者,在每次起点搜索前,将访问数组全部重置为false。这可能更高效,因为创建和释放数组多次可能比较耗时。例如:bool **visited =create_visited(rows,cols);for (int i=0;i <rows;i++){for (int j=0;j <cols;j++){if (matrix[i][j] ==word[0]){memset(visited,0, rows* cols* sizeof(bool));//重置为falseif(dfs(matrix,rows,cols,i,j,word,0, visited)) {free_visited(visited);returntrue;}}}} free_visited(visited); 这样只需要创建一次访问数组,每次起点前重置,这样更高效。但要注意,在DFS过程中,访问数组会被正确标记和回溯,所以重置只需要在每次起点开始时进行。现在,关于代码的具体实现:假设矩阵是一个二维字符数组,比如char**matrix,行数为rows,列数为cols。单词是const char*word,长度为len。需要实现一个函数,返回bool类型,表示是否存在路径。DFS函数的参数包括当前坐标i,j,当前需要匹配的字符位置index,矩阵,访问数组等。例如: booldfs(char**matrix, introws,int cols, inti,int j, constchar *word,int index, bool**visited) {//检查边界条件if(i<0 ||i >=rows ||j <0|| j>= cols) returnfalse;if(visited[i][j])return false;if (matrix[i][j] !=word[index]) returnfalse;//如果已经匹配到最后一个字符if(index== strlen(word)-1)return true;//标记当前位置为已访问visited[i][j] =true;//四个方向intdx[] ={-1,1,0,0};int dy[] ={0,0,-1,1};for(intk =0;k <4; k++) {intni =i +dx[k];int nj= j+ dy[k];if(dfs(matrix,rows,cols,ni,nj,word,index +1, visited)) {//提前返回,不需要继续搜索其他方向visited[i][j]= false; //是否需要?因为整个路径已经找到,可能不需要回溯,但为了正确性,应该在返回前恢复?returntrue;}}//所有方向都失败,回溯visited[i][j]= false;return false; }这里存在一个问题:当在某个方向找到路径后,函数返回true,但此时visited[i][j]仍然被标记为true,是否会影响其他起点的搜索?因为如果访问数组是共享的,那么其他起点的搜索可能会受到影响。但根据前面的设计,每次起点搜索前都会重置访问数组,所以不会有问题。或者,在找到路径后,是否需要将访问数组恢复?例如,在返回true之前,将visited[i][j]设为false?这可能没有必要,因为一旦找到路径,整个函数就会立即返回,而访问数组在下次起点搜索前会被重置。因此,在这种情况下,可以省略恢复,以提升效率。但为了代码的清晰和正确性,最好在回溯时恢复,即使在某些情况下可能不需要。例如,在递归调用返回后,无论是否成功,都应该恢复访问标记,以便同一路径的其他分支可以正确回溯。因此,上面的代码中,在四个方向循环后,恢复标记是正确的做法。但如果某个方向返回true,此时函数会立即返回,而循环中的其他方向不会执行,因此,在返回true之前是否需要恢复标记?例如,当找到路径时,路径上的所有节点的visited标记是否仍然为true?这可能导致后续的搜索(例如另一个起点)受到影响,但如果在每次起点开始时都重置了访问数组,则不会有问题。因此,在这种情况下,可以不需要在返回true前恢复标记,因为访问数组在每次起点搜索前都会被重置。但假设访问数组没有在每次起点前重置,那么可能会有问题。比如,当第一次搜索后,访问数组中的某些位置被标记为true,而后续的起点搜索使用同一个访问数组,那么这些标记会导致错误。因此,正确的做法是,在每次起点搜索前,将访问数组全部重置为false。这样,即使之前的搜索修改了访问数组,也不会影响后续的搜索。因此,在DFS函数中,不需要在返回前恢复标记,因为一旦这次搜索结束,访问数组会被重置,所以恢复与否不影响。不过,为了保持代码的正确性,尤其是在其他可能的情况下,最好在回溯时恢复标记。例如,在DFS函数中,无论是否找到路径,都应该在返回前恢复标记,这样访问数组在递归过程中始终正确管理。例如,在循环之后恢复标记,如当前代码所示。这样,即使同一访问数组被多次使用,也能正确管理状态。现在,主函数的逻辑:bool exist(char**matrix,int rows, intcols,const char*word) {if(matrix== NULL|| rows==0||cols ==0|| word== NULL) returnfalse;intlen =strlen(word);if (len ==0)return true; //空字符串认为存在?bool**visited= create_visited(rows, cols);for (int i=0;i <rows;i++){for (int j=0;j <cols;j++){if (matrix[i][j] ==word[0]){//重置访问数组memset(visited,0,rows *cols *sizeof(bool)); //需要正确计算大小if(dfs(matrix,rows,cols,i,j,word,0,visited)){free_visited(visited);returntrue;}}}}free_visited(visited);return false; }这里需要注意,create_visited函数需要动态分配二维数组,例如:bool **create_visited(introws,int cols) {bool**visited= (bool **)malloc(rows* sizeof(bool *));for (int i=0;i <rows;i++){visited[i] =(bool*)malloc(cols* sizeof(bool));}returnvisited;} voidfree_visited(bool**visited, introws){for (int i=0;i <rows;i++){free(visited[i]);}free(visited);} 另外,memset的使用是否正确?因为visited是一个二维指针,每行是一个数组。所以,在重置时,需要遍历每一行进行memset。例如: for(inti =0;i <rows;i++){memset(visited[i],0,cols *sizeof(bool)); }或者在主函数中,每次起点前使用循环来重置。这可能比memset更正确,因为二维数组的内存可能不是连续的。因此,在每次起点开始时,应该: for(intx=0;x <rows;x++){for (int y=0;y <cols;y++){visited[x][y] =false;}} 或者更高效的方式:for (int x=0;x <rows;x++){memset(visited[x],0,cols *sizeof(bool)); }这样更高效。因此,主函数中的重置部分应改为://重置访问数组for(intx =0;x <rows;x++){memset(visited[x],0, cols* sizeof(bool));} 这样确保每一行都被正确重置。现在,测试一些情况: 案例1:矩阵:AB CES FCSA DE E单词:"ABCCED",应返回true。 DFS路径:A→B→C→C→E→D。 案例2:同一矩阵,单词"SEE",存在路径S→E→E。案例3: 矩阵AA,单词"AAA",不存在,因为不能重复访问。所以应该返回false。 现在,代码是否正确处理这些情况? 对于案例3,当尝试第二个A时,由于已经访问过第一个A,无法继续。因此,代码应返回false。现在,关于性能问题。如果矩阵很大,或者单词很长,DFS可能效率较低。可以考虑剪枝,或者在找到路径后立即返回,这已经在代码中处理了。另一个可能的优化是,预处理,先检查矩阵中是否包含单词的所有字符,否则直接返回false。例如,如果单词中有某个字符在矩阵中不存在,可以直接返回false。这可能在某些情况下减少不必要的搜索。例如,统计每个字符的出现次数,然后比较单词中的字符是否都有足够的数量。不过这可能增加预处理时间,但对于长单词可能有效。例如,在exist函数中,首先统计矩阵中的字符频率,然后检查单词中的每个字符是否都有足够的数量,并且顺序正确。例如,单词的第一个字符必须在矩阵中存在,否则直接返回false。这可能提前终止一些情况。 但用户的问题可能不需要这种优化,所以可能作为后续的改进点。现在,关于BFS的实现。BFS需要保存状态,包括当前位置、已访问的路径、当前匹配的位置等。由于不能重复访问,每个状态需要独立的访问数组,或者使用位图记录访问情况。这在C语言中可能比较麻烦,因为需要为每个状态保存访问情况,导致内存消耗较大。例如,对于每个队列中的节点,需要保存坐标、当前index,以及访问数组的状态。这会导致较高的内存使用,尤其是当矩阵较大时。因此,BFS可能不如DFS高效,但可以避免栈溢出的问题。因此,用户的问题更适合使用DFS实现。总结步骤: 1.遍历所有起点,即与单词首字符匹配的位置。2.对每个起点,使用DFS搜索路径,每次递归检查下一个字符。3.使用访问数组防止重复访问。 4.正确回溯访问数组。5.找到路径后立即返回true,否则继续搜索。现在,编写C代码时需要注意内存管理,避免泄漏。例如,正确释放visited数组。 此外,需要注意字符串的结束符,使用strlen获取单词长度。现在,用户可能需要代码示例,所以需要将上述思路转化为具体的代码,并处理可能的错误。例如,DFS函数的参数是否正确传递?在C语言中,二维数组作为参数传递需要指定列数,或者使用指针的指针。这里假设matrix是char**类型,动态分配的。 综上,最终的代码应该包括exist函数,DFS函数,以及创建和释放visited数组的函数。</think>以下是用C语言实现Word Maze游戏的深度优先搜索(DFS)解决方案: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> bool dfs(char** matrix, int rows, int cols, int i, int j, const char* word, int index, bool** visited) { /* 终止条件 */ if (matrix[i][j] != word[index]) return false; if (index == strlen(word)-1) return true; /* 标记已访问 */ visited[i][j] = true; /* 四个探索方向 */ int dx[] = {-1, 1, 0, 0}; int dy[] = {0, 0, -1, 1}; for (int k = 0; k < 4; k++) { int x = i + dx[k]; int y = j + dy[k]; if (x >= 0 && x < rows && y >= 0 && y < cols && !visited[x][y]) { if (dfs(matrix, rows, cols, x, y, word, index+1, visited)) { return true; } } } /* 回溯 */ visited[i][j] = false; return false; } bool exist(char** matrix, int rows, int cols, char* word) { if (rows == 0 || cols == 0) return false; int len = strlen(word); if (len == 0) return true; /* 创建访问记录矩阵 */ bool** visited = (bool**)malloc(rows * sizeof(bool*)); for (int i = 0; i < rows; i++) { visited[i] = (bool*)malloc(cols * sizeof(bool)); memset(visited[i], 0, cols * sizeof(bool)); } /* 遍历所有可能的起点 */ for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (matrix[i][j] == word[0]) { /* 重置访问记录 */ for (int x = 0; x < rows; x++) memset(visited[x], 0, cols * sizeof(bool)); if (dfs(matrix, rows, cols, i, j, word, 0, visited)) { /* 释放内存 */ for (int x = 0; x < rows; x++) free(visited[x]); free(visited); return true; } } } } /* 释放内存 */ for (int i = 0; i < rows; i++) free(visited[i]); free(visited); return false; } ``` ### 算法说明 1. **DFS核心逻辑**:通过递归实现深度优先搜索,每次探索上下左右四个方向,使用$O(mn)$空间复杂度记录访问路径[^1] 2. **回溯机制**:在探索失败后撤销访问标记,确保后续路径可以重新使用该坐标点 3. **时间复杂度**:最坏情况下为$O(mn \cdot 3^k)$,其中$k$为单词长度,3表示每个节点有3个新方向可探索(排除来路方向) 4. **剪枝优化**:提前终止不匹配的搜索分支,当发现当前字符不匹配时立即返回 ### 测试用例示例 ```c int main() { // 创建3x4矩阵 char* matrix[] = { (char[]){'A','B','C','E'}, (char[]){'S','F','C','S'}, (char[]){'A','D','E','E'} }; printf("%d\n", exist(matrix, 3, 4, "ABCCED")); // 输出1 printf("%d\n", exist(matrix, 3, 4, "SEE")); // 输出1 printf("%d\n", exist(matrix, 3, 4, "ABCB")); // 输出0 return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值