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

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

文章目录

  • 题目描述与示例
    • 题目描述
    • 输入描述
    • 输出描述
    • 示例一
      • 输入
      • 输出
    • 示例二
      • 输入
      • 输出
  • 解题思路
    • 状态更新和回滚写在横向遍历for循环内的回溯写法
    • 状态更新和回滚写在横向遍历for循环外的回溯写法
  • 代码
    • python
    • java
    • cpp
    • 时空复杂度
  • 华为OD算法/大厂面试高频题算法练习冲刺训练

题目描述与示例

题目描述

找到它是个小游戏,你需要在一个矩阵中找到给定的单词 假设给定单词HELLOWORLD,在矩阵中只要能找HELLOWORLD就算通过 注意区分英文字母大小写,并且你只能上下左右行走,不能走回头路

输入描述

输入第一行包含两个整数N M (0 < N, M < 21) 分别表示NM列的矩阵 第二行是长度不超过100的单词W 在整个矩阵中给定单词W只会出现一次 从第3行到第N+2是只包含大小写英文字母的长度为M的字符串矩阵

输出描述

如果能在矩阵中连成给定的单词,则输出给定单词首字母在矩阵中的位置为第几行第几列 否则输出 NO

示例一

输入

5 5
HELLOWORLD
CPUCY
EKLQH
CHELL
LROWO
DGRBC

输出

3 2

示例二

输入

5 5
Helloworld
CPUCh
wolle
orldO
EKLQo
PGRBC

输出

NO

解题思路

注意,本题和LeetCode79. 单词搜索几乎完全一致,唯一的区别在于前者只要求判断是否能够找到该单词,本题还需要输出起始位置。

状态更新和回滚写在横向遍历for循环内的回溯写法

我们需要思考的是,对于这种二维网格如何进行回溯?换句话说,如何构建回溯函数?

在回溯过程中我们需要知道以下信息:

  1. 当前进行到了单词中的哪一个字符?
  2. 当前在网格中搜索到了哪一个位置?
  3. 由于网格中的字符不能重复使用,那么哪一些字符是已经使用过的?
  4. 是否已经在网格中找到了这个单词?

对于第一点,我们的回溯函数中需要存在参数word_idx,来表示待搜索的单词此时遍历到的索引位置。

对于第二点,我们的回溯函数需要传入当前搜索的点的位置(x, y)

对于第三点,这个在二维网格类型的搜索问题中是非常常用的技巧,即构建一个大小和grid一样的check_list

对于第四点,我们可以直接声明一个全局变量isFind来表示是否已经找到该单词

除了这些参数之外,我们还需要传入二维矩阵grid本身,它的大小NM等等。

容易构建出回溯函数如下

DIRECTIONS = [(0,1), (1,0), (-1,0), (0,-1)]def backtracking(grid, N, M, check_list, x, y, word, word_idx):global isFindif word_idx == len(word) - 1:isFind = Truereturnfor dx, dy in DIRECTIONS:nx, ny = x+dx, y+dyif (0 <= nx < len(grid) and 0 <= ny < len(grid[0]) and check_list[nx][ny] == False and grid[nx][ny] == word[word_idx+1]):check_list[nx][ny] = Truebacktracking(grid, N, M, check_list, nx, ny, word, word_idx+1)check_list[nx][ny] = False

容易发现,此处回溯函数的写法,和我们用DFS做二维网格搜索类型的题目是非常类似的。

换句话说,在当前点(x, y)的近邻点上下左右四个方向的选取的这个for循环,实际上就对应着回溯过程中状态树的横向遍历。

和常规DFS解法的区别在于,我们现在搜索的是一条路径,所以我们需要在递归调用backtracking()函数的前后,进行check_list[nx][ny]的状态更新和回滚,来表示近邻点(nx, ny)已经被使用过以及回滚之后再次可以被使用的情况。

在递归调用回溯函数的时候,我们需要将下一个点(nx, ny)以及word的下一个字符索引word_idx+1传入函数中。

回溯的终止条件也非常简单,就是当word_idx已经等于len(word)-1了,说明整个word的所有字符都能够在二维网格中找到,那么修改isFindTrue,同时退出搜索。

而递归入口则需要这样调用

isFind = False
check_list = [[False] * M for _ in range(N)]for i in range(N):for j in range(M):if grid[i][j] == word[0]:check_list[i][j] = Truebacktracking(grid, N, M, check_list, i, j, word, 0)check_list[i][j] = Falseif isFind:print("{} {}".format(i+1, j+1))breakif isFind:break

注意到,由于在回溯函数中修改check_list始终是对(nx, ny)进行修改,所以我们在做起始点搜索的双重循环的时候,在递归函数入口处,需要对起始点(i, j)额外地进行check_list的状态更新和回滚。

状态更新和回滚写在横向遍历for循环外的回溯写法

如果你想直接修改check_list[x][y]而不是修改check_list[nx][ny],那么回溯函数也可以改成这样

DIRECTIONS = [(0,1), (1,0), (-1,0), (0,-1)]def backtracking(grid, N, M, check_list, x, y, word, word_idx):global isFindif word_idx == len(word) - 1:isFind = Truereturncheck_list[x][y] = Truefor dx, dy in DIRECTIONS:nx, ny = x+dx, y+dyif (0 <= nx < len(grid) and 0 <= ny < len(grid[0]) and check_list[nx][ny] == False and grid[nx][ny] == word[word_idx+1]):backtracking(grid, N, M, check_list, nx, ny, word, word_idx+1)check_list[x][y] = False

这样就跟常规的DFS解法更加接近,但和常规的回溯题目的相似性就没那么高了。

因为状态更新和回滚写在了横向遍历for循环的外部。

对应的,由于此处状态的是(x, y)而非(nx, ny),那么在递归入口处就可以不用单独进行(i, j)的更新了。即递归入口可以写为

isFind = False
check_list = [[False] * M for _ in range(N)]for i in range(N):for j in range(M):if grid[i][j] == word[0]:backtracking(grid, N, M, check_list, i, j, word, 0)if isFind:print("{} {}".format(i+1, j+1))breakif isFind:break

代码

python

# 题目:2023B-找到它
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:回溯
# 代码看不懂的地方,请直接在群上提问# 全局的方向数组,表示上下左右移动四个方向
DIRECTIONS = [(0,1), (1,0), (-1,0), (0,-1)]# 构建回溯函数,各个参数的含义为
# grid:         原二维矩阵
# N,M:          原二维矩阵的行数、列数
# check_list:   大小和grid一样的检查列表,用于判断某个点是否已经检查过
# x,y:          当前在grid中的点的坐标
# word:         待搜索的单词
# word_idx:     待搜索的单词此时遍历到的索引位置
def backtracking(grid, N, M, check_list, x, y, word, word_idx):# 声明全局变量isFindglobal isFind# 若此时word_idx等于word的长度-1# 说明word中的所有字母都在grid中找到了# 修改isFind为True,同时终止递归if word_idx == len(word) - 1:isFind = Truereturn# 遍历四个方向,获得点(x,y)的近邻点(nx,ny)for dx, dy in DIRECTIONS:nx, ny = x+dx, y+dy# (nx,ny)必须满足以下三个条件,才可以继续进行回溯函数的递归调用# 1. 不越界;2. 尚未检查过;# 3.在grid中的值grid[nx][ny]为word的下一个字符word[word_idx+1]if 0 <= nx < len(grid) and 0 <= ny < len(grid[0]) and check_list[nx][ny] == False and grid[nx][ny] == word[word_idx+1]:# 状态更新,将点(nx,ny)在check_list中的状态更新为Truecheck_list[nx][ny] = True# 回溯,将点(nx,ny)传入回溯函数中,注意此时word_idx需要+1backtracking(grid, N, M, check_list, nx, ny, word, word_idx+1)# 回滚,将点(nx,ny)在check_list中的状态重新修改回Falsecheck_list[nx][ny] = False# 输入行数和列数
N, M = map(int, input().split())
# 输入待查找的单词
word = input()
# 构建二维网格
grid = list()
for _ in range(N):grid.append(input())# 构建全局变量isFind,初始化为False
isFind = False
# 构建大小和grid一样的检查数组check_list
# 用于避免出现重复检查的情况
check_list = [[False] * M for _ in range(N)]
# 双重遍历整个二维网格grid
for i in range(N):for j in range(M):# 找到点(i,j)等于word的第一个字母# 则点(i,j)可以作为递归的起始位置if grid[i][j] == word[0]:# 将点(i,j)在check_list中设置为已检查过check_list[i][j] = True# 回溯函数递归入口backtracking(grid, N, M, check_list, i, j, word, 0)# 将点(i,j)在check_list中重置为未检查过,因为本次回溯不一定找到答案check_list[i][j] = False# 如果在回溯中,全局变量isFind被改为True,说明找到了单词if isFind:# 输出行数和列数,注意在问题中行数和列数是从1开始计数的# 所以存在一个+1操作print("{} {}".format(i+1, j+1))# 同时可以直接退出循环breakif isFind:breakif not isFind:print("NO")

java

import java.util.Scanner;public class Main {// Global directions array to represent four directions: up, down, left, rightprivate static final int[][] DIRECTIONS = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};private static boolean isFind = false;public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int N = scanner.nextInt();int M = scanner.nextInt();scanner.nextLine(); // Consume newlineString word = scanner.nextLine();char[][] grid = new char[N][M];for (int i = 0; i < N; i++) {String row = scanner.nextLine();for (int j = 0; j < M; j++) {grid[i][j] = row.charAt(j);}}boolean[][] checkList = new boolean[N][M];for (int i = 0; i < N; i++) {for (int j = 0; j < M; j++) {if (grid[i][j] == word.charAt(0)) {checkList[i][j] = true;backtracking(grid, N, M, checkList, i, j, word, 0);checkList[i][j] = false;if (isFind) {System.out.println((i + 1) + " " + (j + 1));return;}}}}if (!isFind) {System.out.println("NO");}}private static void backtracking(char[][] grid, int N, int M, boolean[][] checkList, int x, int y, String word, int wordIdx) {if (wordIdx == word.length() - 1) {isFind = true;return;}for (int[] dir : DIRECTIONS) {int nx = x + dir[0];int ny = y + dir[1];if (nx >= 0 && nx < N && ny >= 0 && ny < M && !checkList[nx][ny] && grid[nx][ny] == word.charAt(wordIdx + 1)) {checkList[nx][ny] = true;backtracking(grid, N, M, checkList, nx, ny, word, wordIdx + 1);checkList[nx][ny] = false;}}}
}

cpp

#include <iostream>
#include <vector>using namespace std;// Global directions array to represent four directions: up, down, left, right
const vector<pair<int, int>> DIRECTIONS = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
bool isFind = false;// Backtracking function
void backtracking(vector<vector<char>>& grid, int N, int M, vector<vector<bool>>& checkList, int x, int y, string& word, int wordIdx) {if (wordIdx == word.length() - 1) {isFind = true;return;}for (const auto& dir : DIRECTIONS) {int nx = x + dir.first;int ny = y + dir.second;if (nx >= 0 && nx < N && ny >= 0 && ny < M && !checkList[nx][ny] && grid[nx][ny] == word[wordIdx + 1]) {checkList[nx][ny] = true;backtracking(grid, N, M, checkList, nx, ny, word, wordIdx + 1);checkList[nx][ny] = false;}}
}int main() {int N, M;cin >> N >> M;string word;cin >> word;vector<vector<char>> grid(N, vector<char>(M));for (int i = 0; i < N; i++) {for (int j = 0; j < M; j++) {cin >> grid[i][j];}}vector<vector<bool>> checkList(N, vector<bool>(M, false));for (int i = 0; i < N; i++) {for (int j = 0; j < M; j++) {if (grid[i][j] == word[0]) {checkList[i][j] = true;backtracking(grid, N, M, checkList, i, j, word, 0);checkList[i][j] = false;if (isFind) {cout << i + 1 << " " << j + 1 << endl;return 0;}}}}if (!isFind) {cout << "NO" << endl;}return 0;
}

时空复杂度

时间复杂度:O(NM3^L)。其中L为单词word的长度,这是一个比较宽松的上界,回溯过程中每一个点都最多有三个分支可以进入。

空间复杂度:O(NM)check_list所占空间。


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

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

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

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/809649.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

M语言前景

M语言作为一种编程语言&#xff0c;其前景可能受到多个因素的影响&#xff0c;包括其应用领域、社区支持、市场需求等。目前&#xff0c;M语言在多个领域都有应用&#xff0c;如智能机器人开发、语音识别系统、图像处理技术、大数据分析、云计算等&#xff0c;这显示出其多功能…

使用Python进行网站爬虫和数据分析

在网络数据的获取和分析过程中&#xff0c;网站爬虫技术是一种常见且有效的手段。本文将介绍如何使用Python编程语言&#xff0c;结合常用的爬虫库和数据分析工具&#xff0c;实现对网站数据的抓取和分析。 1. 准备工作 在开始之前&#xff0c;确保您已经安装了Python解释器&a…

智能加湿器中应用的数字温度传感芯片

随着经济的发展和人民生活水平的提高&#xff0c;人们对生活质量和健康的要求愈来愈高。空气加湿器就是这样慢慢的走进全球的很多家庭当中&#xff0c;成为干燥地区家庭不可缺少的一种小型家电产品。空气加湿器在我国仍属于新兴产物&#xff0c;加大对空气加湿器的研究与开发的…

【how2j练习题】HTML DOM部分阶段练习

练习1 <!-- 验证账号是否已经存在 那么就在js使用简单的验证规则&#xff1a; 如果账号是以a或者A开头的&#xff0c;那么就提示已经存在了。 --> <!-- 1.需要一个输入框和一个按钮 2.按钮上绑上一个事件。 3.编写事件&#xff0c;并输出答案 --><html><…

服务器感染了.rmallox勒索病毒,如何确保数据文件完整恢复?

引言&#xff1a; 随着网络技术的发展&#xff0c;勒索病毒已经成为当今数字时代的一大威胁。近期出现的.rmallox勒索病毒更是引发了广泛关注。本文将深入探讨.rmallox勒索病毒的特点&#xff0c;并提供一系列应对这一威胁的高效策略。如果受感染的数据确实有恢复的价值与必要…

如何打造高度柔性动态的智能仓储物流解决方案?

近年来&#xff0c;仓储物流行业步入自动化系统集成时代&#xff0c;以货架为存储主体的方式逐步发展成为了自动化储方式&#xff0c;核心设备也由货架转变为机器人货架&#xff0c;形成系统集成物流存储体系。河北沃克根据客户需求精准发力&#xff0c;推出了新一代海格里斯智…

TSINGSEE青犀边缘计算AI智能分析网关V4客流统计算法的配置步骤及使用

TSINGSEE青犀AI智能分析网关V4内置了近40种AI算法模型&#xff0c;支持对接入的视频图像进行人、车、物、行为、烟火等实时检测分析&#xff0c;上报识别结果&#xff0c;并能进行语音告警播放。硬件支持RTSP、GB28181协议、以及厂家私有协议接入&#xff0c;可兼容市面上常见的…

十六、计算机视觉-Scharr算子 和 Laplacian算子

文章目录 一、Scharr算子二、Laplacian算子 一、Scharr算子 Scharr算子和Sobel算子原理都一样&#xff0c;它是由Scharr在2002年提出的一种改进的Sobel算子。Scharr算子的优点在于它相对于Sobel算子有更好的旋转不变性和更小的边缘响应误差。 我们看下Scharr算子的水平卷积核&…

SpringBoot + Redisson 限流

Aspect Component public class LimitInterceptor {Autowiredprivate RedissonClient redissonClient;Value("${stnet.api.limit}")private boolean limit;/***配置织入点***/Pointcut("annotation(com.st.microservice.plugin.sso.annotation.Limit)")pub…

MySQL:主键,事件,索引的基础用法(10)

主键 指定某个字段作为主键&#xff0c;这个字段内容无法为空&#xff0c;而且他的内容不能重复作为唯一的标识 主键还有自增和非自增&#xff0c;比如你创建了一个表&#xff0c;你设置了自增&#xff0c;他就会按编号依次自动加一 我创建了一个名为tarro的数据库&#xff…

2813: 【算法思想】【双指针】无重复最长子串

题目描述 给定一个字符串&#xff0c;找出不含有重复字符的 最长子串 的长度。&#xff08;注意&#xff1a;必须是一个子串&#xff0c;不是子序列&#xff09; 输入 一个字符串 输出 最长子串 的长度 示例&#xff1a; 给定 "abcabcbb" &#xff0c;没有重复字…

element-ui使用记录

element-ui的组件名就是类名 样式穿透&#xff08;用来修改没有类名的子组件样式&#xff09; 例如修改头部具名插槽的样式&#xff08;但是无法定位该元素&#xff09; 查看最后生成的html结构中对应的结构&#xff08;这里的头部有类名&#xff0c;可以直接对该类名进行样…

C语言—每日选择题—Day69

第一题 1、以下程序的输出结果是&#xff08; &#xff09; int main() {char arr[2][4];strcpy (arr[0],"you");strcpy (arr[1],"me");arr[0][3]&;printf("%s \n",arr);return 0; } A: you&me B: you C: me D: err 答案及解析 A 这里重…

FFmpeg: 简易ijkplayer播放器实现--03UI界面设计

文章目录 UI设计流程图UI设计界面点击播放功能实现 UI设计流程图 UI设计界面 主界面 控制条 播放列表 画面显示 标题栏 设置界面 提示框 点击播放功能实现 槽函数实现&#xff1a; connect(ui->ctrlBarWind, &CtrlBar::SigPlayOrPause, this, &Main…

zookeeper解析

目录 zookeeper定义 zookeeper定义 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目 Zookeeper工作机制 zookeeper从设计模式角度来理解&#xff1a; 是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它负责存储和管理大家都关心…

DS18B20与单片机的通信、DS18B20采集温度、MODBUS协议、练习框架

我要成为嵌入式高手之4月9日51单片机第四天&#xff01;&#xff01; ———————————————————————————— DS18B20温度传感器 单总线数字温度计 异步的半双工的串行通信 测量范围从-55℃ ~ 125℃&#xff0c;增量值为0.5℃ 要用DS18B20采集温度&am…

Ingress配置优化和追踪

介绍 在传统的业务系统中&#xff0c;应用微服务化后&#xff0c;需要一个统一的入口来将各个服务进行整合&#xff0c;这个入口可以是Nginx、Apache、HAproxy等等。而在K8s中&#xff0c;同样需要一个工具来将应用的各个service整合到统一的入口&#xff0c;这个工具就叫Ingr…

【php快速上手(五)】

目录 PHP快速上手&#xff08;五&#xff09;PHP 运算符1. 算术运算符2. 赋值运算符3. 比较运算符4. 逻辑运算符5. 位运算符6. 递增/递减运算符7. 字符串运算符8. 数组运算符9. 三元运算符&#xff08;条件运算符&#xff09;10. 空合并运算符 条件语句1. if 语句2. else 语句3…

ArcGIS Pro中的3D建模

在本文中,我讲述了我最近一直在探索的在 ArcGIS Pro 中设计 3D 模型的过程。 我的目标是尽可能避免与其他软件交互(即使是专门用于 3D 建模的软件),并利用 Pro 可以提供的可能性。 这个短暂的旅程分为三个不同的阶段:准备、组装和照明。 我们必须使用一些布局可能性以及一…

flask后端+网页前端:基于 socket.io 的双向通信和服务器部署

我想实现的效果是&#xff0c;我的服务器提供两个路由网址&#xff0c;网页A用于拍照、然后录音&#xff0c;把照片和录音传给服务器&#xff0c;服务器发射信号&#xff0c;通知另一个路由的网页B更新&#xff0c;把刚刚传来的照片和录音显示在网页上。 然后网页B用户根据这个…