【Py/Java/C++三种语言OD2023C卷真题】20天拿下华为OD笔试之【回溯】2023C-加密算法【欧弟算法】全网注释最详细分类最全的华为OD真题题解

本文主要是介绍【Py/Java/C++三种语言OD2023C卷真题】20天拿下华为OD笔试之【回溯】2023C-加密算法【欧弟算法】全网注释最详细分类最全的华为OD真题题解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

有LeetCode算法/华为OD考试扣扣交流群可加 948025485
可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳 od1336了解算法冲刺训练

文章目录

  • 题目描述与示例
    • 题目描述
    • 输入描述
    • 输出描述
    • 示例一
      • 输入
      • 输出
    • 示例二
      • 输入
      • 输出
  • 解题思路
  • 代码
    • python
    • Java
    • C++
    • 时空复杂度
  • 华为OD算法/大厂面试高频题算法练习冲刺训练

题目描述与示例

题目描述

有一种特殊的加密算法,明文为一段数字串,经过密码本查找转换,生成另一段密文数字串。规则如下

  1. 明文为一段数字串由0-9组成
  2. 密码本为数字0-9组成的二维数组
  3. 需要按明文串的数字顺序在密码本里找到同样的数字串,密码本里的数字串是由相邻的单元格数字组成,上下和左右是相邻的,注意:对角线不相邻,同一个单元格的数字不能重复使用。
  4. 每一位明文对应密文即为密码本中找到的单元格所在的行和列序号(序号从0开始)组成的两个数字。如明文第iData[i]对应密码本单元格为Book[X][Y],则明文第i位对应的密文为X YXY之间用空格隔开。如果有多条密文,返回字符序最小的密文。如果密码本无法匹配,返回"error".

请你设计这个加密程序。

示例 1:

密码本:

{0,0,2},
{1,3,4},
{6,6,4}

明文"3",密文"1 1"

示例 2:

密码本:

{0,0,2},
{1,3,4},
{6,6,4}

明文"0 3",密文"0 1 1 1"

示例 3:

密码本:

{0,0,2,4}
{1,3,4,6}
{3,4,1,5}
{6,6,6,5}

明文"0 0 2 4",密文"0 0 0 1 0 2 0 3""0 0 0 1 0 2 1 2",返回字典序小的"0 0 0 1 0 2 0 3"

输入描述

第一行输入1个正整数N,代表明文的长度(1 <= N <= 9)

第二行输入N个明文数字组成的序列Data[i](整数,0 <= Data[i] <= 9)

第三行输入1个正整数M,(1 <= M <= 9)

接下来输入一个M*M的矩阵代表密码本Book[i][i],(整数,0 <= Book[i][i] <= 9)

输出描述

如明文 第iData[i]对应密码本单元格为Book[i][j],则明文第i位对应的密文为X YXY之间用空格隔开。如果有多条密文,返回字符序最小的密文。如果密码本无法匹配,返回"error"

示例一

输入

2
0 3
3
0 0 2
1 3 4
6 6 4

输出

0 1 1 1

示例二

输入

4
0 0 2 4
4
0 0 2 4
1 3 4 6
3 4 1 5
6 6 6 5

输出

0 0 0 1 0 2 0 3

解题思路

注意,本题和LeetCode79. 单词搜索、【回溯】2023C-找到它非常类似。唯一的区别是,题目不保证答案是唯一的,当存在多个合适的密文的时候,需要返回字典序最小的那个。

本题基本思路和【回溯】2023C-找到它一致。本题需要着重考虑最小字典序的问题。

这个时候,搜索的方向数组DIRECTIONS里的顺序就非常重要了。假设当前点为(x, y),那么其近邻点为

  • 上方:(x-1, y)
  • 左方:(x, y-1)
  • 右方:(x, y+1)
  • 下方:(x+1, y)

显然,如果存在多个近邻点同时满足下一个字符的时候,按照上、左、右、下这个顺序来搜索的话,一定能够得到最小的字典序,因为坐标为更小字典序的近邻点被优先搜索了

这也是极少数的,我们需要特别注意方向数组DIRECTIONS的顺序的题目。即

DIRECTIONS = [(-1, 0), (0, -1), (0, 1), (1, 0)]

代码

python

# 题目:2023C-加密算法
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:回溯
# 代码看不懂的地方,请直接在群上提问# 全局的方向数组,表示上下左右移动四个方向
# 【特别注意】:此处的顺序是非常重要的,必须按照【上、左、右、下】来排布
# 这样才能使得计算得到的密文一定是字典序最小
DIRECTIONS = [(-1, 0), (0, -1), (0, 1), (1, 0)]# 构建回溯函数,各个参数的含义为
# grid:         原二维矩阵
# M:            原二维矩阵的大小M
# check_list:   大小和grid一样的检查列表,用于判断某个点是否已经检查过
# x,y:          当前在grid中的点的坐标
# s:            待搜索的明文
# s_idx:        待搜索的明文此时遍历到的索引位置
# path:         当前路径
def backtracking(grid, M, check_list, x, y, s, s_idx, path):# 声明全局变量isFindglobal isFind# 如果在之前的回溯中已经找到了最小字典序的密文,直接返回if isFind:return# 若此时s_idx等于s的长度-1,即N-1# 说明s中的所有数字都在grid中找到了# 修改isFind为True,输出答案,同时终止递归if s_idx == N - 1:isFind = Trueprint(" ".join(f"{item[0]} {item[1]}" for item in path))return# 遍历四个方向,获得点(x,y)的近邻点(nx,ny)for dx, dy in DIRECTIONS:nx, ny = x+dx, y+dy# (nx,ny)必须满足以下三个条件,才可以继续进行回溯函数的递归调用# 1. 不越界;2. 尚未检查过;# 3.在grid中的值grid[nx][ny]为s的下一个字符s[s_idx+1]if 0 <= nx < M and 0 <= ny < M and check_list[nx][ny] == False and grid[nx][ny] == s[s_idx+1]:# 状态更新:将点(nx,ny)在check_list中的状态更新为True,更新path末尾check_list[nx][ny] = Truepath.append((nx, ny))# 回溯:将点(nx,ny)传入回溯函数中,注意此时s_idx需要+1backtracking(grid, M, check_list, nx, ny, s, s_idx+1, path)# 回滚:将点(nx,ny)在check_list中的状态重新修改回False,将path末尾的函数弹出check_list[nx][ny] = Falsepath.pop()# 输入明文长度N
N = int(input())
# 输入待查找的明文
s = input().split()
# 输入密码本的行数列数M
M = int(input())
# 构建密码本二维网格
grid = list()
for _ in range(M):grid.append(input().split())# 构建全局变量isFind,初始化为False
isFind = False# 构建大小和grid一样的检查数组check_list
# 用于避免出现重复检查的情况
check_list = [[False] * M for _ in range(M)]
# 双重遍历整个二维网格grid
for i in range(M):for j in range(M):# 找到点(i,j)等于s的第一个数字# 则点(i,j)可以作为递归的起始位置if grid[i][j] == s[0]:# 将点(i,j)在check_list中设置为已检查过check_list[i][j] = True# 回溯函数递归入口,path初始为储存点(i,j)backtracking(grid, M, check_list, i, j, s, 0, [(i, j)])# 将点(i,j)在check_list中重置为未检查过,因为本次回溯不一定找到答案check_list[i][j] = False# 如果在回溯中,全局变量isFind被改为True,说明找到了明文,直接退出循环if isFind:break# 关于i的循环同理,找到明文之后直接退出循环if isFind:break# 如果最终没找到明文,输出error
if not isFind:print("error")

Java

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;public class Main {// 全局的方向数组,表示上下左右移动四个方向// 【特别注意】:此处的顺序是非常重要的,必须按照【上、左、右、下】来排布// 这样才能使得计算得到的密文一定是字典序最小static final int[][] DIRECTIONS = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};static boolean isFind = false;// 构建回溯函数static void backtracking(String[][] grid, int M, boolean[][] checkList, int x, int y, String[] s, int sIdx, List<List<Integer>> path) {// 如果在之前的回溯中已经找到了最小字典序的密文,直接返回if (isFind) return;// 若此时sIdx等于s的长度-1,即N-1// 说明s中的所有数字都在grid中找到了// 修改isFind为True,输出答案,同时终止递归if (sIdx == s.length - 1) {isFind = true;StringBuilder sb = new StringBuilder();for (List<Integer> point : path) {sb.append(point.get(0)).append(" ").append(point.get(1)).append(" ");}System.out.println(sb);return;}// 遍历四个方向,获得点(x,y)的近邻点(nx,ny)for (int[] dir : DIRECTIONS) {int nx = x + dir[0];int ny = y + dir[1];// (nx,ny)必须满足以下三个条件,才可以继续进行回溯函数的递归调用// 1. 不越界;2. 尚未检查过;// 3.在grid中的值grid[nx][ny]为s的下一个字符s[sIdx+1]if (nx >= 0 && nx < M && ny >= 0 && ny < M && !checkList[nx][ny] && grid[nx][ny].equals(s[sIdx + 1])) {// 状态更新:将点(nx,ny)在checkList中的状态更新为True,更新path末尾checkList[nx][ny] = true;List<Integer> point = new ArrayList<>();point.add(nx);point.add(ny);path.add(point);// 回溯:将点(nx,ny)传入回溯函数中,注意此时sIdx需要+1backtracking(grid, M, checkList, nx, ny, s, sIdx + 1, path);// 回滚:将点(nx,ny)在checkList中的状态重新修改回False,将path末尾的点弹出checkList[nx][ny] = false;path.remove(path.size() - 1);}}}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int N = scanner.nextInt();String[] s = new String[N];for (int i = 0; i < N; i++) {s[i] = scanner.next();}int M = scanner.nextInt();String[][] grid = new String[M][M];for (int i = 0; i < M; i++) {for (int j = 0; j < M; j++) {grid[i][j] = scanner.next();}}// 构建大小和grid一样的检查数组checkList// 用于避免出现重复检查的情况boolean[][] checkList = new boolean[M][M];// 双重遍历整个二维网格gridfor (int i = 0; i < M; i++) {for (int j = 0; j < M; j++) {// 找到点(i,j)等于s的第一个数字// 则点(i,j)可以作为递归的起始位置if (grid[i][j].equals(s[0])) {// 将点(i,j)在checkList中设置为已检查过checkList[i][j] = true;List<List<Integer>> path = new ArrayList<>();List<Integer> point = new ArrayList<>();point.add(i);point.add(j);path.add(point);// 回溯函数递归入口,path初始为储存点(i,j)backtracking(grid, M, checkList, i, j, s, 0, path);// 将点(i,j)在checkList中重置为未检查过,因为本次回溯不一定找到答案checkList[i][j] = false;// 如果在回溯中,全局变量isFind被改为True,说明找到了明文,直接退出循环if (isFind) {break;}}}// 关于i的循环同理,找到明文之后直接退出循环if (isFind) {break;}}// 如果最终没找到明文,输出errorif (!isFind) {System.out.println("error");}}
}

C++

#include <iostream>
#include <vector>
#include <string>using namespace std;// 全局的方向数组,表示上下左右移动四个方向
// 【特别注意】:此处的顺序是非常重要的,必须按照【上、左、右、下】来排布
// 这样才能使得计算得到的密文一定是字典序最小
const vector<vector<int>> DIRECTIONS = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};
bool isFind = false;// 构建回溯函数
void backtracking(vector<vector<string>>& grid, int M, vector<vector<bool>>& checkList, int x, int y, vector<string>& s, int sIdx, vector<vector<int>>& path) {// 如果在之前的回溯中已经找到了最小字典序的密文,直接返回if (isFind) return;// 若此时sIdx等于s的长度-1,即N-1// 说明s中的所有数字都在grid中找到了// 修改isFind为True,输出答案,同时终止递归if (sIdx == s.size() - 1) {isFind = true;for (int i = 0; i < path.size(); ++i) {cout << path[i][0] << " " << path[i][1] << " ";}cout << endl;return;}// 遍历四个方向,获得点(x,y)的近邻点(nx,ny)for (auto& dir : DIRECTIONS) {int nx = x + dir[0];int ny = y + dir[1];// (nx,ny)必须满足以下三个条件,才可以继续进行回溯函数的递归调用// 1. 不越界;2. 尚未检查过;// 3.在grid中的值grid[nx][ny]为s的下一个字符s[sIdx+1]if (nx >= 0 && nx < M && ny >= 0 && ny < M && !checkList[nx][ny] && grid[nx][ny] == s[sIdx + 1]) {// 状态更新:将点(nx,ny)在checkList中的状态更新为True,更新path末尾checkList[nx][ny] = true;path.push_back({nx, ny});// 回溯:将点(nx,ny)传入回溯函数中,注意此时sIdx需要+1backtracking(grid, M, checkList, nx, ny, s, sIdx + 1, path);// 回滚:将点(nx,ny)在checkList中的状态重新修改回False,将path末尾的点弹出checkList[nx][ny] = false;path.pop_back();}}
}int main() {int N;cin >> N;vector<string> s(N);for (int i = 0; i < N; ++i) {cin >> s[i];}int M;cin >> M;vector<vector<string>> grid(M, vector<string>(M));for (int i = 0; i < M; ++i) {for (int j = 0; j < M; ++j) {cin >> grid[i][j];}}// 构建大小和grid一样的检查数组checkList// 用于避免出现重复检查的情况vector<vector<bool>> checkList(M, vector<bool>(M, false));// 双重遍历整个二维网格gridfor (int i = 0; i < M; ++i) {for (int j = 0; j < M; ++j) {// 找到点(i,j)等于s的第一个数字// 则点(i,j)可以作为递归的起始位置if (grid[i][j] == s[0]) {// 将点(i,j)在checkList中设置为已检查过checkList[i][j] = true;vector<vector<int>> path = {{i, j}};// 回溯函数递归入口,path初始为储存点(i,j)backtracking(grid, M, checkList, i, j, s, 0, path);// 将点(i,j)在checkList中重置为未检查过,因为本次回溯不一定找到答案checkList[i][j] = false;// 如果在回溯中,全局变量isFind被改为True,说明找到了明文,直接退出循环if (isFind) {break;}}}// 关于i的循环同理,找到明文之后直接退出循环if (isFind) {break;}}// 如果最终没找到明文,输出errorif (!isFind) {cout << "error" << endl;}return 0;
}

时空复杂度

时间复杂度:O(M^2*3^N)。其中N为密文s的长度,这是一个比较宽松的上界,回溯过程中每一个点都最多有三个分支可以进入。

空间复杂度:O(M^2)check_list所占空间。


华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务300+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

这篇关于【Py/Java/C++三种语言OD2023C卷真题】20天拿下华为OD笔试之【回溯】2023C-加密算法【欧弟算法】全网注释最详细分类最全的华为OD真题题解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/897186

相关文章

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例:

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma