资讯动态

蓝桥杯备赛Day1:P1101单词方阵,用方向枚举取代DFS

发布时间:2026/10/19 18:06:25 来源:尧图企业网站定制
1. 为什么第一天的第一题选了P1101 单词方阵如果今天是你的蓝桥杯备赛第一天我建议你先把 P1101 单词方阵 这道题做透。原因很简单它不是难题却把搜索类题目里最容易被忽略的细节全暴露出来了——方向怎么枚举、边界怎么判、标记怎么打。我第一次做这道题时大概花了三十分钟回头看真正有价值的不是提交通过那一刻而是把“为什么这样写”想清楚的过程。这篇就按我 Day1 的实际备赛思路从题目拆解、方向枚举、完整代码到踩坑复盘完整过一遍。1.1 这道题在蓝桥杯备赛里的定位蓝桥杯省赛的题目大致分几类填空、模拟、搜索、图论、动态规划。P1101 这种“字符矩阵里找单词”的题属于搜索题里的入门精品。它不涉及复杂的剪枝也不需要状态压缩或记忆化但能把二维数组遍历、方向数组、边界判断一次练熟。备赛第一天选它就像训练营开营先练站姿后面跑动、对抗才有基础。很多同学觉得搜索题难是因为一上来就碰迷宫最短路径、连通块数量这些题既要用 DFS 又要处理访问标记和回溯很容易劝退。但 P1101 把搜索题的骨架单独拎出来你只需要从起点出发沿着固定的方向一步步核对字符是否匹配全程没有任何决策分支。这种“低难度但高信息量”的题目恰好适合做备赛第一天的热手题。1.2 读完题之后的第一反应别急着写 DFS看到“单词方阵”四个字多数人的第一反应是写 DFS 回溯从某个字母出发上下左右搜索尝试拼出目标单词。但我必须泼一盆冷水这道题用真正的 DFS反而容易把自己绕晕。题目里的隐藏单词是yizhong它在方阵中只能沿着八个方向之一直线放置。什么叫直线放置就是从起点开始一直往同一个方向走中间不拐弯连续拼出 y-i-z-h-o-n-g 这七个字母。换句话说这条路径在起点和方向决定后就已经完全确定不存在“下一步有多个选择”的情况。DFS 回溯的价值在于处理“多分支、可回退”的状态空间而这里根本没有分支硬上回溯只会让你在方向判断上做大量无用功。所以正确的心理建设是这道题真正要练的是“方向枚举 字符串比对”。把八个方向看成八条可能的路径每个方向都试一遍能匹配就打个标记。想明白这一点代码量瞬间少一半。1.3 样例输入里藏着的关键信息题目输入第一行是方阵边长 n后面跟着 n 行字母。输出是一个同样大小的方阵如果某个字母属于某个完整的yizhong单词就原样输出否则输出星号*。这里有一个容易被忽略的细节同一块字母可能被多条匹配路径共用。比如两个单词从不同起点出发在中途某几个格子上重叠这些格子仍然应该保留。因为你最终判断的是“这个格子是否属于任意一条完整路径”而不是“这个格子是否只属于一条路径”。这也决定了解法里要单独开一个标记数组而不是在匹配失败时把标记撤销。另一个细节是方向一致性从起点到终点七个字母必须严格朝同一方向排列。有的同学写检查函数时每走一步都重新枚举方向结果把直线路径走成了折线路径这种错误在样例上经常看不出来但一提交就错。2. P1101 题目到底在说什么输入、输出与规则拆解2.1 字母方阵的规则与隐藏单词P1101 的题干用一句话概括是给你一个 n×n 的字母方阵方阵里可能藏着若干个单词yizhong它们可以横着、竖着、斜着排列但每条单词必须是一条直线。你需要做的是把所有属于这些单词的字母保留下来把不相关的字母改成星号。n 的范围在常见评测数据里一般不超过 100所以最暴力的复杂度也是 O(n² × 8 × 7)也就是最多几十万次字符比较在任何语言里都能轻松通过。理解这一点很重要如果你在写这道题时还想各种剪枝优化那就是把一门心思都花错了地方。蓝桥杯省赛里很多题的时间限制其实很宽松关键不是把程序跑得多快而是把规则理解得足够准确。为了让你真正吃透规则我随手设计一个最小可验证场景。假设 n7方阵第一行正好放了一个横向的yizhong主对角线上又放了一个斜向的yizhong它俩在y这个起点上重叠。正确输出里第一行和对角线上的字母都应该被保留其他格子全是*。如果你写的程序总是漏掉斜向或重叠的字母多半就是规则理解出了问题。2.2 八方向增量表是整道题的灵魂二维方阵中从某个格子 (x, y) 出发可以走八个方向。为了在代码里统一处理一般会用方向增量数组表示水平向右(1, 0)水平向左(-1, 0)垂直向下(0, 1)垂直向上(0, -1)右下方(1, 1)左上方(-1, -1)右上方(1, -1)左下方(-1, 1)这里使用的坐标约定是 x 表示行下标、y 表示列下标。不同的编程习惯可能把行和列反过来但只要你始终保持“同一套坐标体系”结果都一样。为什么方向增量数组这么重要因为它把八个方向统一成了一组循环避免你在检查时写八个 if 语句。如果不用数组你可能会写出checkRight、checkLeft、checkDown... 八个函数代码又长又容易在复制粘贴时改错一个符号。方向数组的排列顺序也值得养成固定习惯。我的习惯是先把水平方向写完、再写垂直方向、最后写四个斜方向这样排查时不容易漏。你可以把方向表的注释直接写在数组旁边比赛时能省下大量回忆时间。2.3 标记数组 vs 访问数组这道题里它们的职责完全不同很多做过迷宫题的同学看到“标记”两个字会条件反射地写 visited 数组并在回溯时将其重置。这是 P1101 最大的陷阱之一。在迷宫题里visited 数组的作用是防止同一轮搜索重复走同一个格子所以在从一个分支退出时会“恢复现场”。在这道题里我们要的不是探索路径而是“验证一条已经固定的路径是否成立”。一旦验证成功这条路径上的格子就要永久保留不管之后其他路径是否经过它们都不能再撤销。所以我更建议把标记数组命名为keep或used语义是“这个格子最终要保留输出”。它不参与匹配过程只在最后输出阶段起作用。匹配过程本身只依赖原矩阵board不需要任何访问状态。3. 核心解法方向枚举比 DFS 更实在3.1 算法流程只有四步整个算法可以压缩成四句话遍历每一个格子如果当前字母不是y跳过如果是y就枚举八个方向对每个方向检查从当前格开始沿该方向走七步是否依次等于yizhong的七个字符成立则标记路径。为什么要从y开始而不是从任意字母开始因为目标单词首字母固定是y。以y为起点能剪掉大量无意义的检查。同理你还可以在检查前先算一下“这个方向第七个位置是否越界”但这不是必须的完整循环里每一步都会做边界判断越界自然返回失败。3.2 完整 C 实现及逐段说明先看代码再逐段拆解。我习惯用#include bits/stdc.h参赛因为它能一次性覆盖常用头文件省得比赛时纠结该引哪些库。函数命名尽量直白方便赛后回看。#include bits/stdc.h using namespace std; const int MAXN 110; char board[MAXN][MAXN]; bool keep[MAXN][MAXN]; int n; string word yizhong; int dx[8] {1, -1, 0, 0, 1, 1, -1, -1}; int dy[8] {0, 0, 1, -1, 1, -1, 1, -1}; bool canMatch(int x, int y, int d) { for (int k 0; k (int)word.size(); k) { int nx x k * dx[d]; int ny y k * dy[d]; if (nx 0 || nx n || ny 0 || ny n) return false; if (board[nx][ny] ! word[k]) return false; } return true; } void markPath(int x, int y, int d) { for (int k 0; k (int)word.size(); k) { int nx x k * dx[d]; int ny y k * dy[d]; keep[nx][ny] true; } } int main() { cin n; for (int i 0; i n; i) cin board[i]; memset(keep, 0, sizeof(keep)); for (int i 0; i n; i) { for (int j 0; j n; j) { if (board[i][j] ! y) continue; for (int d 0; d 8; d) { if (canMatch(i, j, d)) { markPath(i, j, d); } } } } for (int i 0; i n; i) { for (int j 0; j n; j) { cout (keep[i][j] ? board[i][j] : *); } cout \n; } return 0; }这段代码需要解释几个关键点。第一canMatch函数里的 k 循环从 0 到 6恰好对应单词的七个字母。因为k * dx[d]会把当前位置逐步沿着方向 d 推进所以不需要维护一个“当前坐标”变量。这样写的好处是即使中途越界或字符不匹配整个函数直接返回 false不会污染任何状态。第二markPath和canMatch的循环结构几乎一模一样只是把比较字符换成了置标记。代码冗余但非常清晰比赛时间紧张时宁可用一点冗余换正确性。还有一点要注意word.size()返回的是无符号整数循环变量 k 可能被隐式转换所以我在代码里写了(int)word.size()。这个问题平时不痛不痒但在某些编译器警告全开的环境下会浪费时间。3.3 Python 实现版本如果你准备用 Python 参赛这段逻辑可以写得更短。Python 的列表推导式在处理输出时很方便。n int(input()) board [list(input().strip()) for _ in range(n)] keep [[False] * n for _ in range(n)] word yizhong directions [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)] def check(x, y, dx, dy): for k in range(len(word)): nx x k * dx ny y k * dy if not (0 nx n and 0 ny n): return False if board[nx][ny] ! word[k]: return False return True def mark(x, y, dx, dy): for k in range(len(word)): keep[x k * dx][y k * dy] True for i in range(n): for j in range(n): if board[i][j] y: for dx, dy in directions: if check(i, j, dx, dy): mark(i, j, dx, dy) for i in range(n): print(.join(board[i][j] if keep[i][j] else * for j in range(n)))Python 代码里我把directions写成了元组列表读起来更直观。check和mark的实现与 C 完全等价。需要提醒的是Python 的嵌套循环性能虽然一般但这道题 n 最大只有 100完全不需要担心超时。3.4 为什么每一步都查边界比先查终点更稳有些同学会把检查函数写成先判断终点是否越界再判断中间字符。比如检查方向 d 时直接计算第 6 步的坐标如果越界就跳过。这种做法在 n 很小时没问题但不够通用。更稳的写法是像上面这样每一步都检查边界因为一旦目标单词的长度不止 7 个字符你的终点判断逻辑就得同步改而逐步检查的代码只需要改一下word的内容。这种“让代码跟着数据走”的思维在蓝桥杯备赛后期会越来越重要。毕竟省赛里很多题目都不只一组测试数据边界条件稍有偏差就可能全盘皆输。4. 踩坑实录我在第一版代码里犯的三个错误4.1 用 DFS 做路径搜索方向越走越乱我的第一版代码大概长这样从某个y开始递归尝试八个方向每次递归再重新枚举方向试图拼出yizhong。结果就是一条路径可能在第三个字符时换了个方向比如向右走得到i接着却向下走得到z这时候程序还会继续比较虽然单词长度累加上去了但方向已经不对。最终输出的是明明方阵里有正儿八经的横向单词程序却把一条 L 形路径认成了匹配项。排查时我把每一步的坐标打印出来才意识到问题出在“每层递归都重新枚举方向”。后来我改成在检查路径前先把方向定死把方向参数传进检查函数问题就消失了。这个坑之所以常见是因为很多同学的条件反射就是先写 DFS。但 P1101 的题眼是“同一个方向连续走”不是“通过不同方向拼接单词”。想通这一点代码从递归 30 行变成了循环 15 行。4.2 错误恢复现场导致已经匹配的路径被撤销第二版我吸取了教训不再用 DFS改用方向枚举。但我在标记时犯了一个错把keep数组当成 visited配合一个栈在匹配失败时把已经打的标记全部撤销。正常匹配成功时没问题但一旦某个方向检查到一半发现字符不对就会把之前几个格子的标记清掉。这会导致一个非常隐蔽的 bug如果同一格被两条路径共用第一条路径成功打上了标记第二条路径检查到一半失败顺便把第一条的标记清掉最终输出少保留了一些字母。排查这个问题时我一度怀疑是方向数组写错了后来用了一个只有两个重叠单词的小数据发现keep数组的值在中途被改成 false才意识到是恢复现场的锅。修复方法很简单标记只在全部匹配成功后统一打上匹配过程中不要碰keep数组。4.3 边界判断的坐标顺序和符号写反了第三个坑出现在坐标方向上。我的方向数组一开始写成了dx {0, 0, 1, -1, 1, 1, -1, -1}dy {1, -1, 0, 0, 1, -1, 1, -1}看起来和标准写法只是行、列互换了但因为后面check函数里所有比较都用nx 0 || nx n || ny 0 || ny n行列互换理论上也能通过。真正出问题的是其中一个斜方向我把 (1, -1) 写成了 (1, 1)导致所有右上方向的单词全部漏掉。这种错误在样例上可能完全看不出来因为样例不会把八个方向的单词都覆盖到。我后来养成一个习惯自己构造一个包含八方向的小方阵每个方向各放一个词跑一遍程序用肉眼对比输出。这个方法救了我很多次强烈建议你也试一下。5. 从 P1101 抽象出通用套路字符矩阵直线匹配的一类题5.1 通用代码骨架起点枚举 方向枚举 长度比较P1101 的解法可以抽象成一个四段式骨架适用于所有“字符矩阵中找直线单词”的题目定义好方向增量数组 dx / dy。遍历所有格子找到和目标单词首字母相同的起点。枚举八个方向对每个方向检查整条路径是否完全匹配。匹配成功则对路径上的格子做标记或计数。这个骨架几乎不需要改动就能应对很多变体。比如题目让你统计单词出现次数只要在匹配成功时返回计数而不是打标记再比如题目让你输出所有匹配起点的坐标只要在匹配成功时记录 (i, j) 和方向 d。5.2 与可转弯的单词搜索题做对比很多题库里都有一道“单词搜索”题给定一个字母网格和一个单词判断单词是否可以通过相邻格子的路径组成路径允许上下左右转弯。很多同学做完 P1101 后会把两道题混在一起实际上它们的解法完全不同。下面这个表格能帮你快速区分对比维度P1101 单词方阵可转弯单词搜索路径形状固定直线方向不变可上下左右任意转弯核心算法方向枚举 字符串比对DFS 回溯是否需要恢复现场不需要需要状态变量起点 方向当前坐标 已匹配长度典型复杂度O(n² × 8 × L)O(n² × 4^L)从这个表能看出来P1101 的时间复杂度是线性的而单词搜索是指数级的。所以遇到题目时先判断路径能否转弯再决定上不上 DFS。这个判断往往只需要 10 秒却能避免你走一大段弯路。5.3 常见变形多单词、四方向、回文串如果你把 target 从yizhong改成变量代码立刻就能处理任意固定长度的单词。如果再进一步把方向数组从八个方向改成四方向就变成了“直线四方向”的简化版。还见过一种变体不是找固定单词而是找方阵里所有“长度为 m 的回文串”本质上也还是直线枚举只是终点的匹配条件从等于某个字符串变成了首尾相等。理解这些变形之后你会发现 P1101 并不是一道孤立题它属于“字符矩阵直线模式”这个大家族。备赛时可以按这个思路把类似题归类这样刷一道题就等于储备了一个题型的解法。6. 蓝桥杯备赛 Day1 复盘时间分配与后续计划6.1 一道入门题应该花多久我给自己定的规则是第一遍独立思考不超过 20 分钟如果在 20 分钟内没有完整思路就停下来看题解但只看思路提示不看完整代码然后自己重新写。P1101 属于“15 分钟内应该能想到方向枚举”的题如果你卡在 DFS 的泥潭里超过 20 分钟说明需要把“直线匹配”和“路径搜索”这两类问题在脑子里分开。写完代码并提交通过后再花 10 分钟做复盘我自己第一版错在哪里正确的题解比我的版本好在哪如果把 n 扩大到 1000 还能不能跑这三个问题能帮你把一道简单题的价值放到最大。6.2 从这道题提炼的备赛日志模板从 Day1 开始我每天会写一个极简备赛日志包含五个要点日期、题号、知识点标签我的第一反应是否选对了算法标准解法的关键一步是什么本轮踩的坑及根因一句话总结该题型的记忆点这个模板不会花太多时间但能让你在考前一周快速回滚所有题型。比如 P1101 的一句话记忆点我会写“直线单词不用 DFS方向定死七步比对对了就标记。”考前翻到这句整个解法就能在十几秒内重新浮现。备赛第一天的收获不在于你真的掌握了一个算法而在于你开始建立一种“刷题直觉”看到字符矩阵先想方向数组看到路径可转弯才想回溯。这种直觉需要大量题目去喂养但起点就在 P1101 这道看似简单的单词方阵里。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑