高效搜索二维矩阵多目标:25×25网格字符串序列定位方案咨询
嘿,这个场景我之前做类似的字符网格序列匹配时碰到过,刚好可以给你几个实用的思路。针对你25×25的网格(不算特别大,但高效性还是要兼顾),分享几个可行的方案,适配你定位多组字符串序列元素位置的需求:
首先可以先做一次全网格遍历,给每个字符建立一个坐标列表映射,比如用字典char_positions,键是字符,值是该字符在网格中所有的(x,y)坐标对。这样当你要找某个序列的时候,比如序列seq = [c1, c2, ..., cn],可以:
- 先拿到
c1的所有坐标,作为序列的起点候选 - 对每个起点,用DFS/回溯法,依次查找下一个字符
c2是否在当前位置的相邻区域(上下左右/斜向,看你的序列是否允许斜向连续),以此类推直到匹配完整个序列 - 一旦找到完整匹配,就记录下所有中间位置
这个方法的优势是:预处理只需要O(N²)时间(N=25,也就是625次操作,非常快),后续查找序列的时候,因为每个字符的候选位置被提前过滤了,不需要盲目遍历整个网格。比如如果某个字符在网格里只有3个位置,那只需要从这3个位置开始验证,大大减少无效搜索。
举个伪代码片段:
# 预处理字符位置映射 from collections import defaultdict char_positions = defaultdict(list) for x in range(25): for y in range(25): char = grid[x][y] char_positions[char].append( (x,y) ) # 查找序列的函数 def find_sequence(seq): if not seq: return [] paths = [] # 遍历第一个字符的所有起点 for start_pos in char_positions.get(seq[0], []): def backtrack(current_pos, current_path, idx): if idx == len(seq)-1: paths.append(current_path.copy()) return # 遍历8个方向(如果允许斜向),可根据需求改成4个方向 directions = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)] for dx, dy in directions: nx = current_pos[0] + dx ny = current_pos[1] + dy # 检查边界、字符匹配、是否重复访问(按需开启) if 0<=nx<25 and 0<=ny<25 and grid[nx][ny] == seq[idx+1] and (nx,ny) not in current_path: current_path.append( (nx,ny) ) backtrack( (nx,ny), current_path, idx+1 ) current_path.pop() backtrack(start_pos, [start_pos], 0) return paths
如果你的两组序列数量比较多(比如每组包含很多子序列),用AC自动机会更高效。它可以一次性把所有要找的序列构建成一个前缀树,然后遍历网格的每个位置,以该位置为起点,向所有允许的方向(4/8方向)移动,同时在AC自动机中匹配,一旦匹配到完整的序列,就记录对应的位置路径。
这个方法的好处是:当需要匹配多个序列时,不需要逐个序列单独处理,而是一次遍历网格就能完成所有匹配,适合批量处理。对于25×25的网格来说,遍历成本完全可控。
实现思路大概是:
- 把所有要找的序列加入AC自动机的模式集合
- 遍历网格的每个(x,y)作为起点
- 从起点出发,沿着每个方向逐步移动,每走一步就检查当前路径的字符是否在AC自动机中匹配到了某个序列
- 如果匹配成功,记录下该路径的所有坐标
如果你只需要找到序列的终点位置,或者想快速验证某个序列是否存在,DP是个不错的选择。我们可以定义dp[k][x][y]表示:序列的第k个字符是否能出现在网格的(x,y)位置。
初始化:对于序列的第一个字符seq[0],所有网格中等于seq[0]的(x,y)位置,dp[0][x][y] = True
状态转移:对于k≥1,dp[k][x][y] = True当且仅当:
- 网格(x,y)的字符等于
seq[k] - 存在相邻的位置(x', y')(符合你的连续规则),使得
dp[k-1][x'][y'] = True
最后,dp[len(seq)-1][x][y]为True的位置,就是序列的终点,你可以反向回溯找到完整的路径。
这个方法的时间复杂度是O(N² * L),其中L是序列的长度,对于25×25和一般长度的序列来说,计算量非常小,而且可以快速批量处理多个序列。
因为你的网格只有25×25,其实哪怕用最朴素的暴力遍历(每个位置作为起点,尝试匹配序列)也能跑得很快,但上面的方法能让你在序列变长、数量变多的时候依然保持高效:
- 如果序列是固定方向(比如只能水平/垂直),可以在遍历的时候只检查对应方向,减少不必要的计算
- 如果序列中的字符重复率很低,方案1的预处理映射会特别高效,因为候选起点很少
- 如果需要多次查询不同的序列,方案1的预处理只需要做一次,后续查询都是O(M*L),M是起点数量,L是序列长度
你提到已经实现了基础步骤,可以看看当前的基础逻辑更贴近哪个方案,然后在此之上扩展,比如如果已经有了网格的读取逻辑,直接加预处理映射和回溯验证会非常快。
内容的提问来源于stack exchange,提问作者23k

