1. 加密算法题目解析与实现思路
这个加密算法题目来自华为OD机试真题,考察的是二维矩阵中的路径搜索和字符串匹配能力。题目要求我们根据给定的密码本(一个数字矩阵)和明文数字串,找到对应的密文输出。
1.1 题目核心要求
题目规则可以拆解为以下几个关键点:
- 密码本是一个M×M的二维数字矩阵
- 明文是一个由0-9数字组成的序列
- 密文生成规则:
- 需要在密码本中找到与明文完全匹配的数字序列
- 匹配时数字必须相邻(上下左右,不包括对角线)
- 每个单元格的数字不能重复使用
- 如果有多条匹配路径,选择字典序最小的
- 如果无法匹配,返回"error"
1.2 算法设计思路
这个问题本质上是一个二维矩阵中的路径搜索问题,可以使用深度优先搜索(DFS)或广度优先搜索(BFS)来解决。考虑到需要找到所有可能的路径并选择字典序最小的,DFS是更合适的选择。
算法实现步骤:
- 预处理密码本,记录每个数字出现的位置
- 对明文第一个数字,获取所有可能的起始位置
- 从每个起始位置开始DFS搜索,寻找匹配明文序列的路径
- 在搜索过程中维护已访问的单元格,避免重复使用
- 收集所有有效路径,按字典序排序后返回第一个
2. Python实现详解
2.1 数据结构设计
首先我们需要设计合适的数据结构来存储密码本和中间结果:
python复制from collections import defaultdict
class Encryptor:
def __init__(self):
self.rows = 0
self.cols = 0
self.book = []
self.num_pos = defaultdict(list)
2.2 核心搜索算法实现
DFS搜索的实现需要注意剪枝和回溯:
python复制def dfs(self, data, index, x, y, visited, path):
# 终止条件:匹配完成
if index == len(data):
return path
# 检查边界和是否匹配
if x < 0 or x >= self.rows or y < 0 or y >= self.cols:
return None
if visited[x][y] or self.book[x][y] != data[index]:
return None
# 标记已访问
visited[x][y] = True
current_path = path + [(x, y)]
# 四个方向搜索
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] # 右、下、左、上(按字典序)
results = []
for dx, dy in directions:
res = self.dfs(data, index+1, x+dx, y+dy, visited, current_path)
if res:
results.append(res)
# 回溯
visited[x][y] = False
# 返回字典序最小的路径
if results:
return min(results)
return None
2.3 完整加密函数实现
将各个部分组合起来完成加密功能:
python复制def encrypt(self, data):
# 预处理数字位置
self.num_pos = defaultdict(list)
for i in range(self.rows):
for j in range(self.cols):
self.num_pos[self.book[i][j]].append((i, j))
# 处理明文
data = [int(c) for c in data.split()] if isinstance(data, str) else data
# 获取所有可能的起始位置
start_positions = self.num_pos.get(data[0], [])
all_paths = []
# 对每个起始位置进行DFS
for x, y in start_positions:
visited = [[False]*self.cols for _ in range(self.rows)]
path = self.dfs(data, 0, x, y, visited, [])
if path:
all_paths.append(path)
# 处理结果
if not all_paths:
return "error"
# 找到字典序最小的路径
min_path = min(all_paths)
return ' '.join(f"{x} {y}" for x, y in min_path)
3. JavaScript实现详解
3.1 类结构设计
JavaScript实现采用类似的思路,但需要注意语言特性的差异:
javascript复制class Encryptor {
constructor() {
this.rows = 0;
this.cols = 0;
this.book = [];
this.numPos = new Map();
}
}
3.2 DFS搜索实现
JavaScript的DFS实现需要注意数组和对象的处理:
javascript复制dfs(data, index, x, y, visited, path) {
// 终止条件
if (index === data.length) {
return path;
}
// 边界检查
if (x < 0 || x >= this.rows || y < 0 || y >= this.cols) {
return null;
}
if (visited[x][y] || this.book[x][y] !== data[index]) {
return null;
}
// 标记已访问
visited[x][y] = true;
const currentPath = [...path, [x, y]];
// 四个方向搜索(按字典序)
const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]];
const results = [];
for (const [dx, dy] of directions) {
const res = this.dfs(data, index + 1, x + dx, y + dy, visited, currentPath);
if (res) {
results.push(res);
}
}
// 回溯
visited[x][y] = false;
// 返回字典序最小的路径
if (results.length > 0) {
return results.sort()[0];
}
return null;
}
3.3 完整加密函数
JavaScript版本的加密函数:
javascript复制encrypt(data) {
// 预处理数字位置
this.numPos = new Map();
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.cols; j++) {
const num = this.book[i][j];
if (!this.numPos.has(num)) {
this.numPos.set(num, []);
}
this.numPos.get(num).push([i, j]);
}
}
// 处理输入数据
const inputData = typeof data === 'string'
? data.split(' ').map(Number)
: data;
// 获取起始位置
const startPositions = this.numPos.get(inputData[0]) || [];
const allPaths = [];
// 对每个起始位置进行DFS
for (const [x, y] of startPositions) {
const visited = Array.from({length: this.rows},
() => new Array(this.cols).fill(false));
const path = this.dfs(inputData, 0, x, y, visited, []);
if (path) {
allPaths.push(path);
}
}
// 处理结果
if (allPaths.length === 0) {
return "error";
}
// 找到字典序最小的路径
const minPath = allPaths.sort()[0];
return minPath.map(pos => pos.join(' ')).join(' ');
}
4. 算法优化与性能考虑
4.1 剪枝优化
在实际实现中,我们可以进行一些优化来提高性能:
- 提前终止:一旦找到字典序最小的路径就可以立即返回,不需要搜索所有可能性
- 方向顺序优化:按照字典序搜索方向(右、下、左、上),这样第一个找到的路径就是字典序最小的
- 预处理优化:对于大型密码本,可以预先建立数字到位置的映射
4.2 边界情况处理
需要特别注意以下边界情况:
- 明文长度为1的情况
- 密码本中不存在明文第一个数字的情况
- 密码本中存在多个相同数字的情况
- 大尺寸密码本(200×200)的性能问题
4.3 复杂度分析
假设密码本大小为M×M,明文长度为N:
- 时间复杂度:最坏情况下是O(M² × 4^N),因为每个位置最多有4个方向选择
- 空间复杂度:O(M² + N),主要用于存储访问记录和递归栈
5. 测试用例与验证
5.1 示例测试
验证题目给出的示例:
python复制# 示例1
book = [
[0, 0, 2],
[1, 3, 4],
[6, 6, 4]
]
encryptor = Encryptor()
encryptor.rows = encryptor.cols = 3
encryptor.book = book
print(encryptor.encrypt([3])) # 应输出 "1 1"
# 示例2
print(encryptor.encrypt("0 3")) # 应输出 "0 1 1 1"
5.2 边界测试
测试各种边界情况:
python复制# 测试不存在的情况
print(encryptor.encrypt([9])) # 应输出 "error"
# 测试多个路径的情况
book2 = [
[1, 1, 1],
[1, 2, 1],
[1, 1, 1]
]
encryptor.book = book2
print(encryptor.encrypt([1, 2, 1])) # 应输出字典序最小的路径
5.3 性能测试
对于大型密码本的测试:
python复制import random
# 生成100×100的随机密码本
big_book = [[random.randint(0, 9) for _ in range(100)] for _ in range(100)]
encryptor.rows = encryptor.cols = 100
encryptor.book = big_book
# 测试短明文
short_data = [big_book[0][0], big_book[0][1]]
print(encryptor.encrypt(short_data))
6. 常见问题与解决方案
6.1 路径重复问题
问题:如何确保不重复使用同一个单元格?
解决方案:使用visited二维数组记录访问状态,在DFS进入时标记,回溯时取消标记。
6.2 字典序比较
问题:如何比较两条路径的字典序?
解决方案:将路径转换为字符串形式直接比较,例如路径[(0,1),(1,1)]转换为"0 1 1 1"。
6.3 大矩阵性能
问题:对于200×200的大矩阵如何优化性能?
优化建议:
- 预处理时只记录可能用到的数字位置
- 实现迭代DFS代替递归DFS减少栈开销
- 添加启发式规则提前终止不可能路径
6.4 多语言实现差异
Python和JavaScript实现的主要差异:
- JavaScript没有元组类型,使用数组代替
- JavaScript的Map与Python的defaultdict用法不同
- JavaScript的数组处理更繁琐,需要更多样板代码
7. 实际应用与扩展
这个加密算法虽然题目设定简单,但涉及的技术点在实际开发中很常见:
- 路径搜索:广泛应用于游戏AI、路线规划等领域
- 回溯算法:解决约束满足问题的通用技术
- 字典序处理:在排序和比较场景中经常遇到
可能的扩展方向:
- 支持更复杂的移动规则(如对角线移动)
- 添加权重系统,寻找最优路径而非字典序最小
- 实现并行搜索加速大型密码本的处理