【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

相关文章

eclipse如何运行springboot项目

《eclipse如何运行springboot项目》:本文主要介绍eclipse如何运行springboot项目问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目js录当在eclipse启动spring boot项目时出现问题解决办法1.通过cmd命令行2.在ecl

Java中的Closeable接口及常见问题

《Java中的Closeable接口及常见问题》Closeable是Java中的一个标记接口,用于表示可以被关闭的对象,它定义了一个标准的方法来释放对象占用的系统资源,下面给大家介绍Java中的Clo... 目录1. Closeable接口概述2. 主要用途3. 实现类4. 使用方法5. 实现自定义Clos

Jvm sandbox mock机制的实践过程

《Jvmsandboxmock机制的实践过程》:本文主要介绍Jvmsandboxmock机制的实践过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、背景二、定义一个损坏的钟1、 Springboot工程中创建一个Clock类2、 添加一个Controller

SpringBoot实现文件记录日志及日志文件自动归档和压缩

《SpringBoot实现文件记录日志及日志文件自动归档和压缩》Logback是Java日志框架,通过Logger收集日志并经Appender输出至控制台、文件等,SpringBoot配置logbac... 目录1、什么是Logback2、SpringBoot实现文件记录日志,日志文件自动归档和压缩2.1、

MQTT SpringBoot整合实战教程

《MQTTSpringBoot整合实战教程》:本文主要介绍MQTTSpringBoot整合实战教程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录MQTT-SpringBoot创建简单 SpringBoot 项目导入必须依赖增加MQTT相关配置编写

spring-gateway filters添加自定义过滤器实现流程分析(可插拔)

《spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔)》:本文主要介绍spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔),本文通过实例图... 目录需求背景需求拆解设计流程及作用域逻辑处理代码逻辑需求背景公司要求,通过公司网络代理访问的请求需要做请

Spring Security介绍及配置实现代码

《SpringSecurity介绍及配置实现代码》SpringSecurity是一个功能强大的Java安全框架,它提供了全面的安全认证(Authentication)和授权(Authorizatio... 目录简介Spring Security配置配置实现代码简介Spring Security是一个功能强

SpringCloud使用Nacos 配置中心实现配置自动刷新功能使用

《SpringCloud使用Nacos配置中心实现配置自动刷新功能使用》SpringCloud项目中使用Nacos作为配置中心可以方便开发及运维人员随时查看配置信息,及配置共享,并且Nacos支持配... 目录前言一、Nacos中集中配置方式?二、使用步骤1.使用$Value 注解2.使用@Configur

Java 中的跨域问题解决方法

《Java中的跨域问题解决方法》跨域问题本质上是浏览器的一种安全机制,与Java本身无关,但Java后端开发者需要理解其来源以便正确解决,下面给大家介绍Java中的跨域问题解决方法,感兴趣的朋友一起... 目录1、Java 中跨域问题的来源1.1. 浏览器同源策略(Same-Origin Policy)1.

Java 关键字transient与注解@Transient的区别用途解析

《Java关键字transient与注解@Transient的区别用途解析》在Java中,transient是一个关键字,用于声明一个字段不会被序列化,这篇文章给大家介绍了Java关键字transi... 在Java中,transient 是一个关键字,用于声明一个字段不会被序列化。当一个对象被序列化时,被