1. 题目解析与加密算法设计思路
这道题目来自华为OD机考双机位C卷,考察的是对二维矩阵的搜索算法实现能力。题目要求我们实现一个特殊的加密算法,将明文数字串转换为密文坐标串。下面我将详细解析题目要求并设计解决方案。
1.1 题目核心要求
加密算法的核心规则可以归纳为以下几点:
- 密码本是一个M×M的二维数字矩阵
- 明文是一个由0-9数字组成的序列
- 密文是明文数字在密码本中对应位置的坐标序列
- 明文数字在密码本中的查找必须满足相邻条件(上下左右,不包括对角线)
- 同一个单元格不能重复使用
- 当存在多个可能的密文时,返回字典序最小的那个
1.2 算法设计思路
这个问题本质上是一个在二维矩阵中查找特定路径的问题,与经典的"单词搜索"问题类似。我们可以采用深度优先搜索(DFS)算法来解决:
-
预处理阶段:首先扫描密码本,记录每个数字出现的位置,建立数字到位置列表的映射,这样可以快速定位起始点。
-
搜索阶段:
- 从明文的第一个数字开始,在密码本中找到所有匹配的位置作为起点
- 对于每个起点,使用DFS搜索后续数字,确保相邻性和不重复使用单元格
- 在搜索过程中维护当前路径的坐标序列
-
结果处理:
- 收集所有可能的密文路径
- 按照字典序排序,选择最小的作为最终结果
- 如果没有找到任何路径,返回"error"
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 详细实现与代码解析
2.1 数据结构设计
首先我们需要设计合适的数据结构来存储和处理密码本:
python复制# 密码本表示为二维列表
password_book = [
[0, 0, 2],
[1, 3, 4],
[6, 6, 4]
]
# 建立数字到位置的映射字典
# 格式:{数字: [(row1, col1), (row2, col2), ...]}
num_pos_map = {}
for i in range(len(password_book)):
for j in range(len(password_book[0])):
num = password_book[i][j]
if num not in num_pos_map:
num_pos_map[num] = []
num_pos_map[num].append((i, j))
2.2 DFS搜索算法实现
DFS是实现路径搜索的核心算法,需要考虑以下几点:
- 避免重复使用单元格
- 只允许上下左右移动
- 及时剪枝优化性能
python复制def dfs(password_book, plain, index, path, visited, result):
if index == len(plain):
result.append(path.copy())
return
current_num = plain[index]
last_row, last_col = path[-1] if path else (None, None)
# 如果是第一个数字,从所有匹配位置开始
if index == 0:
for pos in num_pos_map.get(current_num, []):
visited.add(pos)
path.append(pos)
dfs(password_book, plain, index+1, path, visited, result)
path.pop()
visited.remove(pos)
else:
# 检查四个方向的相邻单元格
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
new_row, new_col = last_row + dr, last_col + dc
if (0 <= new_row < len(password_book) and
0 <= new_col < len(password_book[0]) and
password_book[new_row][new_col] == current_num and
(new_row, new_col) not in visited):
visited.add((new_row, new_col))
path.append((new_row, new_col))
dfs(password_book, plain, index+1, path, visited, result)
path.pop()
visited.remove((new_row, new_col))
2.3 主函数与结果处理
主函数负责整合各个部分,处理输入输出并选择最终结果:
python复制def encrypt(plain, password_book):
# 建立数字位置映射
num_pos_map = {}
for i in range(len(password_book)):
for j in range(len(password_book[0])):
num = password_book[i][j]
if num not in num_pos_map:
num_pos_map[num] = []
num_pos_map[num].append((i, j))
result = []
dfs(password_book, plain, 0, [], set(), result)
if not result:
return "error"
# 将路径转换为坐标字符串并排序
str_results = []
for path in result:
str_path = []
for row, col in path:
str_path.extend([str(row), str(col)])
str_results.append(" ".join(str_path))
# 按字典序排序并返回第一个
str_results.sort()
return str_results[0]
3. 多语言实现要点
3.1 Java实现注意事项
在Java中实现时需要注意:
- 使用ArrayList存储可变长度的路径
- 使用HashSet来记录已访问的位置
- 注意Java中二维数组的行列表示方式
java复制// Java中的DFS核心部分
private void dfs(int[][] passwordBook, int[] plain, int index,
List<int[]> path, Set<String> visited, List<List<int[]>> result) {
if (index == plain.length) {
result.add(new ArrayList<>(path));
return;
}
int currentNum = plain[index];
if (index == 0) {
for (int[] pos : numPosMap.getOrDefault(currentNum, new ArrayList<>())) {
String key = pos[0] + "," + pos[1];
if (!visited.contains(key)) {
visited.add(key);
path.add(pos);
dfs(passwordBook, plain, index+1, path, visited, result);
path.remove(path.size()-1);
visited.remove(key);
}
}
} else {
int[] last = path.get(path.size()-1);
int[][] directions = {{-1,0},{1,0},{0,-1},{0,1}};
for (int[] dir : directions) {
int newRow = last[0] + dir[0];
int newCol = last[1] + dir[1];
if (newRow >= 0 && newRow < passwordBook.length &&
newCol >= 0 && newCol < passwordBook[0].length &&
passwordBook[newRow][newCol] == currentNum) {
String key = newRow + "," + newCol;
if (!visited.contains(key)) {
visited.add(key);
path.add(new int[]{newRow, newCol});
dfs(passwordBook, plain, index+1, path, visited, result);
path.remove(path.size()-1);
visited.remove(key);
}
}
}
}
}
3.2 C++实现优化技巧
C++实现可以利用STL和更高效的内存管理:
- 使用vector存储路径和结果
- 使用unordered_set记录访问位置
- 通过引用传递减少拷贝开销
cpp复制// C++中的DFS函数
void dfs(vector<vector<int>>& passwordBook, vector<int>& plain, int index,
vector<pair<int,int>>& path, unordered_set<string>& visited,
vector<vector<pair<int,int>>>& result) {
if (index == plain.size()) {
result.push_back(path);
return;
}
int currentNum = plain[index];
if (index == 0) {
for (auto& pos : numPosMap[currentNum]) {
string key = to_string(pos.first) + "," + to_string(pos.second);
if (visited.find(key) == visited.end()) {
visited.insert(key);
path.push_back(pos);
dfs(passwordBook, plain, index+1, path, visited, result);
path.pop_back();
visited.erase(key);
}
}
} else {
auto [lastRow, lastCol] = path.back();
vector<pair<int,int>> directions = {{-1,0},{1,0},{0,-1},{0,1}};
for (auto [dr, dc] : directions) {
int newRow = lastRow + dr;
int newCol = lastCol + dc;
if (newRow >= 0 && newRow < passwordBook.size() &&
newCol >= 0 && newCol < passwordBook[0].size() &&
passwordBook[newRow][newCol] == currentNum) {
string key = to_string(newRow) + "," + to_string(newCol);
if (visited.find(key) == visited.end()) {
visited.insert(key);
path.emplace_back(newRow, newCol);
dfs(passwordBook, plain, index+1, path, visited, result);
path.pop_back();
visited.erase(key);
}
}
}
}
}
3.3 JavaScript实现特点
JavaScript实现需要注意:
- 使用数组和对象作为主要数据结构
- 注意深拷贝问题,避免引用共享
- 使用JSON.stringify简化位置比较
javascript复制// JavaScript中的DFS实现
function dfs(passwordBook, plain, index, path, visited, result) {
if (index === plain.length) {
result.push([...path]);
return;
}
const currentNum = plain[index];
if (index === 0) {
const positions = numPosMap[currentNum] || [];
for (const pos of positions) {
const key = JSON.stringify(pos);
if (!visited.has(key)) {
visited.add(key);
path.push(pos);
dfs(passwordBook, plain, index + 1, path, visited, result);
path.pop();
visited.delete(key);
}
}
} else {
const [lastRow, lastCol] = path[path.length - 1];
const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
for (const [dr, dc] of directions) {
const newRow = lastRow + dr;
const newCol = lastCol + dc;
if (newRow >= 0 && newRow < passwordBook.length &&
newCol >= 0 && newCol < passwordBook[0].length &&
passwordBook[newRow][newCol] === currentNum) {
const pos = [newRow, newCol];
const key = JSON.stringify(pos);
if (!visited.has(key)) {
visited.add(key);
path.push(pos);
dfs(passwordBook, plain, index + 1, path, visited, result);
path.pop();
visited.delete(key);
}
}
}
}
}
4. 性能优化与边界处理
4.1 算法优化策略
- 提前终止:一旦找到字典序最小的路径就可以提前终止搜索,因为后续路径的字典序不会更小
- 剪枝优化:在DFS过程中,如果当前路径的字典序已经大于已找到的最小路径,可以提前终止该分支的搜索
- 并行搜索:对于大型密码本,可以考虑并行处理不同起始点的搜索
4.2 边界条件处理
在实际编码中需要特别注意以下边界情况:
- 明文长度为1的特殊情况
- 密码本为空的情况
- 明文数字在密码本中不存在的情况
- 密码本中数字唯一但路径不连续的情况
- 大型密码本(200×200)的性能问题
4.3 错误处理与输入验证
健壮的程序应该包含以下验证:
- 验证明文数字是否都在0-9范围内
- 验证密码本是否为正方形矩阵
- 验证明文长度与输入N是否一致
- 验证密码本行数与输入M是否一致
python复制def validate_input(N, plain, M, password_book):
if N != len(plain):
raise ValueError("明文长度与N不匹配")
if M != len(password_book):
raise ValueError("密码本行数与M不匹配")
for row in password_book:
if len(row) != M:
raise ValueError("密码本不是正方形矩阵")
for num in plain:
if not 0 <= num <= 9:
raise ValueError("明文包含非数字字符")
5. 测试用例与调试技巧
5.1 典型测试用例设计
-
基本功能测试:
- 单数字明文
- 双数字明文,简单路径
- 多数字明文,唯一路径
-
边界条件测试:
- 明文数字不在密码本中
- 密码本中数字唯一但路径不连续
- 最大尺寸密码本(200×200)
-
特殊场景测试:
- 多条可能路径的情况
- 需要回溯的复杂路径
- 密码本中数字分布稀疏的情况
5.2 调试技巧与常见问题
- 路径可视化:打印或可视化搜索路径,帮助理解算法行为
- 小规模测试:先用小密码本(3×3)测试基本功能
- 性能分析:对于大型输入,分析算法时间复杂度和瓶颈
- 常见错误:
- 忘记标记已访问位置导致无限循环
- 字典序比较错误
- 边界条件处理不完整
调试提示:在DFS函数中添加打印语句,输出当前搜索状态和路径,这对理解算法执行流程非常有帮助。
6. 复杂度分析与扩展思考
6.1 时间复杂度分析
该算法的时间复杂度主要取决于:
- 密码本大小M×M
- 明文长度N
- 密码本中数字的分布情况
最坏情况下时间复杂度为O(M^2 × 4^N),因为:
- 每个数字在密码本中最多有M^2个可能位置
- 每个位置有4个可能方向
- 需要搜索N层
但在实际应用中,由于数字分布限制和剪枝优化,实际性能会好很多。
6.2 空间复杂度分析
空间复杂度主要来自:
- 存储密码本:O(M^2)
- DFS调用栈:O(N)
- 存储结果路径:O(N × K),K为解的数量
6.3 算法扩展与变种
- 允许对角线移动:修改相邻方向定义,增加4个对角线方向
- 加权路径选择:为每个移动赋予不同权重,寻找最优权重路径而非字典序最小
- 模糊匹配:允许一定程度的数字差异,寻找最接近的路径
- 三维密码本:扩展到三维空间中的路径搜索
在实际应用中,这种算法思想可以扩展到:
- 图像处理中的特征追踪
- 游戏中的路径寻找
- 密码学中的复杂加密方案
- 生物信息学中的序列比对
7. 完整代码实现(Python示例)
以下是Python的完整实现,包含所有辅助函数和主逻辑:
python复制def encrypt(plain, password_book):
# 建立数字到位置的映射
num_pos_map = {}
for i in range(len(password_book)):
for j in range(len(password_book[0])):
num = password_book[i][j]
if num not in num_pos_map:
num_pos_map[num] = []
num_pos_map[num].append((i, j))
result = []
def dfs(index, path, visited):
if index == len(plain):
result.append(path.copy())
return
current_num = plain[index]
if index == 0:
for pos in num_pos_map.get(current_num, []):
if pos not in visited:
visited.add(pos)
path.append(pos)
dfs(index + 1, path, visited)
path.pop()
visited.remove(pos)
else:
last_row, last_col = path[-1]
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
new_row, new_col = last_row + dr, last_col + dc
if (0 <= new_row < len(password_book) and
0 <= new_col < len(password_book[0]) and
password_book[new_row][new_col] == current_num and
(new_row, new_col) not in visited):
visited.add((new_row, new_col))
path.append((new_row, new_col))
dfs(index + 1, path, visited)
path.pop()
visited.remove((new_row, new_col))
dfs(0, [], set())
if not result:
return "error"
# 转换为字符串并排序
str_results = []
for path in result:
str_path = []
for row, col in path:
str_path.extend([str(row), str(col)])
str_results.append(" ".join(str_path))
str_results.sort()
return str_results[0]
def main():
import sys
input = sys.stdin.read().split()
ptr = 0
N = int(input[ptr])
ptr += 1
plain = list(map(int, input[ptr:ptr+N]))
ptr += N
M = int(input[ptr])
ptr += 1
password_book = []
for _ in range(M):
row = list(map(int, input[ptr:ptr+M]))
password_book.append(row)
ptr += M
print(encrypt(plain, password_book))
if __name__ == "__main__":
main()
8. 实际应用与经验分享
在实际编程竞赛和面试中,这类题目考察的核心能力包括:
- 对二维矩阵的操作能力
- 深度优先搜索算法的实现
- 边界条件的全面考虑
- 代码的整洁度和可读性
我在实际解决这类问题时总结了几点经验:
- 先理清题意:花时间彻底理解题目要求,特别是移动规则和输出格式
- 小规模手动验证:用示例手动模拟算法执行过程,确保理解正确
- 模块化设计:将问题分解为预处理、搜索、结果处理等独立模块
- 及早考虑优化:对于大型输入,从一开始就考虑性能优化策略
- 全面测试:设计各种边界条件的测试用例,确保代码健壮性
重要提示:在华为OD机考中,除了正确性,代码风格和注释也会影响评分。建议适当添加关键注释,保持代码整洁。
