LeetCode-题目整理【10】:单词搜索

先补充一些小知识:

dfs和回溯的区别

深度优先搜索(DFS)和回溯是两种常用的算法思想,它们在解决问题时有一些相似之处,但也有一些不同之处。

  1. 深度优先搜索(DFS)是一种 用于遍历或搜索图、树或其他数据结构的算法。 它从一个起始节点开始,沿着一条路径尽可能深地搜索,直到无法继续或达到目标节点。然后,它回溯到之前的节点,并尝试其他的路径。DFS 是一种 递归的算法,通过调用自身来实现深度搜索。DFS 的特点是先深度后回溯。

  2. 回溯算法是一种解决问题的通用算法,它通过尝试不同的选择来找到解。回溯算法通常用于组合问题、排列问题、搜索问题等。 在回溯算法中,我们逐步构建解,并在每一步尝试不同的选择,如果当前的选择导致无法找到解,那么我们回溯到上一步并尝试其他的选择。回溯算法通常使用 递归 来实现。回溯的特点是试错和撤销。

  3. 总结来说,DFS 是一种用于遍历或搜索特定数据结构的算法,而回溯是一种通用的解决问题的算法思想。DFS 可以看作是一种特殊的回溯算法,它在实现过程中使用了回溯的思想。

  4. 在实际应用中,DFS 和回溯通常会结合使用。例如,在图的深度优先搜索中,可以使用回溯来记录访问过的节点,并在回溯时撤销访问过的节点。在排列组合问题中,也可以使用回溯来生成所有可能的组合,并在回溯时撤销选择。

因此下面这两道单词搜索的题目,因为是图的形式,因此使用DFS

  1. 单词搜索
    中等
    给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
    单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
    示例 1:
    输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
    输出:true
    示例 2:
    输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “SEE”
    输出:true
    示例 3:
    输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCB”
    输出:false
//使用DFS算法(特殊的回溯算法)func exist(board [][]byte, word string) bool {rows, cols := len(board), len(board[0])var dfs func(row, col, index int) booldfs = func(row, col, index int) bool {// 边界条件检查if row < 0 || row >= rows || col < 0 || col >= cols || board[row][col] != word[index] {return false}// 如果已经匹配到最后一个字符,返回 true(因为在board[row][col] != word[index]已经对字母进行判断)if index == len(word)-1 {return true}// 保存当前字符,避免重复使用temp := board[row][col]// 标记当前字符已使用board[row][col] = '.'// 递归调用上下左右四个方向if dfs(row-1, col, index+1) || dfs(row+1, col, index+1) || dfs(row, col-1, index+1) || dfs(row, col+1, index+1) {return true}// 恢复原始字符,进行回溯(主要是当网格中存在字母相同时,最开始选到的字母并不符合条件,只能跳出递归,从新选择起点)board[row][col] = tempreturn false}// 遍历整个二维网格(目的是选择起点位置,如果在index=0时返回false,那么就不是起点,继续遍历)for i := 0; i < rows; i++ {for j := 0; j < cols; j++ {if dfs(i, j, 0) {return true}}}return false
}
  1. 单词搜索 II
    困难
    给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
    单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
    示例 1:
    输入:board = [[“o”,“a”,“a”,“n”],[“e”,“t”,“a”,“e”],[“i”,“h”,“k”,“r”],[“i”,“f”,“l”,“v”]], words = [“oath”,“pea”,“eat”,“rain”]
    输出:[“eat”,“oath”]
    示例 2:
    输入:board = [[“a”,“b”],[“c”,“d”]], words = [“abcb”]
    输出:[]
func findWords(board [][]byte, words []string) []string {rows, cols := len(board), len(board[0])result := []string{}// 定义一个辅助函数,用于进行深度优先搜索var dfs func(row, col, index int, word string)dfs = func(row, col, index int, word string) {// 边界条件检查if row < 0 || row >= rows || col < 0 || col >= cols || board[row][col] == '#' || board[row][col] != word[index] {return}// 如果已经匹配到最后一个字符,将当前单词添加到结果列表中if index == len(word)-1 {result = append(result, word)return}// 保存当前字符,避免重复使用temp := board[row][col]// 标记当前字符已使用board[row][col] = '#'// 递归调用上下左右四个方向dfs(row-1, col, index+1, word)dfs(row+1, col, index+1, word)dfs(row, col-1, index+1, word)dfs(row, col+1, index+1, word)// 恢复原始字符,进行回溯board[row][col] = temp}// 遍历单词列表for _, word := range words {// 遍历整个二维网格,以每个位置作为起点位置调用 dfs 函数for i := 0; i < rows; i++ {for j := 0; j < cols; j++ {dfs(i, j, 0, word)}}}// 去重结果列表中的重复单词uniqResult := make(map[string]bool)for _, word := range result {uniqResult[word] = true}finalResult := []string{}for word := range uniqResult {finalResult = append(finalResult, word)}return finalResult
}//但是时间超出限制,因此需要添加trie树

虽然上述的代码过程是正确的,仅使用到DFS,和第一题的解法类似,但是超出了时间限制,因此需要减少遍历的时间,引入Trie树(前缀树)

  1. Trie 树(也称为前缀树)是一种用于高效存储和搜索字符串的数据结构。 在这个问题中,使用 Trie 树的原因是为了加速单词的匹配。
  2. 在给定的二维网格中,需要搜索是否存在给定的单词。如果使用简单的暴力搜索方法,对于每个单词都需要遍历整个二维网格,时间复杂度将非常高。
  3. 使用 Trie 树可以极大地减少搜索的时间复杂度。 通过构建 Trie 树,我们可以将单词的前缀存储在 Trie 树中,然后在搜索过程中,只需要在 Trie 树中进行匹配即可,避免了不必要的遍历操作。
  4. 具体来说,在构建 Trie 树时,我们可以将所有的单词插入到 Trie 树中,每个节点表示一个字符。然后,在搜索过程中,我们可以根据当前位置的字符在 Trie 树中进行匹配,如果匹配成功,则继续搜索下一个字符;如果匹配失败,则可以直接返回,无需继续搜索。
  5. 通过使用 Trie 树,可以将搜索的时间复杂度降低到 O(n),其中 n 是所有单词的总长度。
type TrieNode struct {children [26]*TrieNodeword     string
}func findWords(board [][]byte, words []string) []string {rows, cols := len(board), len(board[0])result := []string{}root := buildTrie(words)var dfs func(row, col int, node *TrieNode)dfs = func(row, col int, node *TrieNode) {// 边界条件检查if row < 0 || row >= rows || col < 0 || col >= cols || board[row][col] == '#' {return}// 获取当前字符ch := board[row][col]// 检查当前字符是否在 Trie 树中node = node.children[ch-'a']if node == nil {return}// 更新结果列表if node.word != "" {result = append(result, node.word)node.word = "" // 避免重复添加单词}// 保存当前字符,避免重复使用board[row][col] = '#'// 递归调用上下左右四个方向dfs(row-1, col, node)dfs(row+1, col, node)dfs(row, col-1, node)dfs(row, col+1, node)// 恢复原始字符,进行回溯board[row][col] = ch}// 遍历整个二维网格,以每个位置作为起点位置调用 dfs 函数for i := 0; i < rows; i++ {for j := 0; j < cols; j++ {dfs(i, j, root)}}return result
}func buildTrie(words []string) *TrieNode {root := &TrieNode{}for _, word := range words {node := rootfor _, ch := range word {index := ch - 'a'if node.children[index] == nil {node.children[index] = &TrieNode{}}node = node.children[index]}node.word = word}return root
}//使用了 Trie 树(前缀树)来加速单词的匹配。首先,我们先构建一个 Trie 树,将所有的单词插入到 Trie 树中。然后,我们遍历整个二维网格,以每个位置作为起点位置调用深度优先搜索(DFS)。//在 dfs 函数中,我们首先检查当前位置是否越界,如果越界则直接返回。然后,我们获取当前位置的字符,并检查该字符是否在 Trie 树中。如果不在,则直接返回。//如果当前位置的字符在 Trie 树中,我们将当前位置的字符标记为已使用(例如用 #),然后递归调用 dfs 函数,继续在上、下、左、右四个方向上进行搜索。//在递归调用之前,我们需要更新 Trie 树的节点,将其移动到下一层节点。如果移动后的节点表示一个单词,则将该单词添加到结果列表中,并将该节点的 word 字段置为空字符串,以避免重复添加单词。//在递归调用之后,我们需要恢复当前位置的字符,进行回溯。//最后,在主函数中,我们遍历每个起点位置,并调用 dfs 函数进行搜索。将找到的单词添加到结果列表中,并返回结果列表。

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

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

相关文章

LeetCode第559题 - N 叉树的最大深度

题目 解答 class Solution {public int maxDepth(Node root) {if (root null) {return 0;}if (root.children null || root.children.isEmpty()) {return 1;}int max Integer.MIN_VALUE;for (Node node : root.children) {max Math.max(maxDepth(node), max);}return max…

Python 列表应用案例:输入10个整数,计算平均值、方差和标准差,找出最大值和最小值

题目&#xff1a;输入10个整数&#xff0c;计算平均值、方差和标准差&#xff0c;找出最大值和最小值。 方差和标准差公式&#xff1a; Var ( X ) 1 n ∑ ( X i − X ˉ ) 2 \text{Var}(X) \frac{1}{n} \sum (X_i - \bar{X})^2 Var(X)n1​∑(Xi​−Xˉ)2 SD ( X ) Var ( X…

三篇论文联合复现:高比例新能源下考虑需求侧响应和智能软开关的配电网重构程序代码!

适用平台&#xff1a;MatlabYalmipCplex 程序在高比例新能源接入的情况下提出了考虑需求响应&#xff08;DR&#xff09;和智能软开关&#xff08;SOP&#xff09;的多时段主动配电网重构策略&#xff0c;进一步降低配电系统重构费用&#xff0c;减少弃风率和弃光率&#xff1…

深度学习与图像描述生成——图像描述生成方法(4)

目录 一、基于模板的方法 1.1 定义 1.2 原理 1.3 关键技术 1.4 发展历程 1.5 应用场景 1.6 特征 二、基于检索的方法 2.1 定义 2.2 原理 2.3 关键技术 2.4 发展历程 2.5 应用场景 2.6 特征 三、基于编码器-解码器架构的方法 3.1 定义 3.2 原理 3.3 关键技术 …

金融OCR领域实习日志(二)——四种OCR模型效果测试(附图)

文章目录 四种模型ocr效果简单测试模型场景1.paddle框架下PP-OCRv31.1.效果如下&#xff1a;1.2.总结 2.paddle框架下ppocr_server_v22.1.效果如下2.2.总结 3.CnOCR3.1.效果如下3.2.总结 4.TesseractOCR4.1.效果如下4.2.总结 5.后续想法 四种模型ocr效果简单测试 模型 PP-OCR…

【RT-DETR有效改进】 | 主干篇 | RevColV1可逆列网络(特征解耦助力小目标检测)

前言 大家好&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持ResNet32、ResNet101和PP…

JavaScript(JS)和TypeScript(TS)的区别

JavaScript&#xff08;JS&#xff09;和TypeScript&#xff08;TS&#xff09;都是编程语言&#xff0c;它们都可以用于构建交互式的Web应用程序。虽然它们有很多相似之处&#xff0c;但也有一些重要的区别。 JavaScript&#xff08;JS&#xff09; JavaScript是一种解释型脚…

Java面试提纲

JDK 1 jdk1.8版本后的新特性有哪些? Java Development Kit (JDK) 1.8&#xff08;也称为Java 8&#xff09;在2014年3月发布&#xff0c;引入了许多重要的新特性&#xff0c;以下是其中的一些关键特性&#xff1a; Lambda表达式&#xff1a; Java 8引入了lambda表达式&#x…

nginx复现负载均衡案例

这里是下载好了docker&#xff0c;并显示了下镜像这里是拉到了nginx的镜像这里是把容器起来&#xff0c;-itd是容器关闭后销毁这里是显示起来的容器进入到这个容器里面查看许多命令用不了&#xff0c;应该想办法把docker里的文件夹映射到物理机中 这里是如果访问6666端口那么隧…

【ARM 嵌入式 编译系列 3.7 -- newlib 库文件与存根函数 stubs 详细介绍】

请阅读【嵌入式开发学习必备专栏 之 ARM GCC 编译专栏】 文章目录 newlib 库文件介绍资源使用平台支持功能性能许可证兼容性系统调用函数介绍系统调用存根 stubs 详细介绍为什么需要系统调用存根(Stubs)?常见的系统调用存根如何实现系统调用存根如何告知编译器使用自定义存根…

cartographer离线建图报错:data_.trajectory_nodes.SizeOfTrajectoryOrZero

cartographer离线建图报错: data_.trajectory_nodes.SizeOfTrajectoryOrZero [FATAL] [1706177325.876019302, 1706015603.398505596]: F0125 18:08:45.000000 17607 pose_graph_2d.cc:1314] Check failed: data_.trajectory_nodes.SizeOfTrajectoryOrZero(trajectory_id) &…

C语言实现插入排序算法(附带源代码)

插入排序 插入排序&#xff08;英语&#xff1a;Insertion Sort&#xff09;是一种简单直观的排序算法。它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上&#xff0c;通常…

Nginx编译安装以及负载均衡配置(Ubuntu 22.04)

目录 Nginx编译安装以及负载均衡配置 Ubuntu 22.04.1 LTS 编译安装 nginx-1.22.1 1.安装依赖包 2. 下载nginx 3. 编译安装 报错解决 解决问题2 4.安装 5启动Nginx&#xff1a; 负载均衡 负载均衡算法 轮询 加权负载均衡 ip_hash算法 算法进行配置演示 加权负载均衡 轮询 IP 哈希…

vue中使用canvas给图片绘制水印,即使下载图片也是带水印的

先看效果 话不多说直接上组件 1、Watermark.vue <template><div><canvas ref"canvas" :width"width" :height"height"></canvas></div> </template><script>export default {props: {// 图片地址ur…

【第一天】蓝桥杯备战

题 1、 门牌号2、卡片3、分数 1、 门牌号 https://www.lanqiao.cn/problems/592/learning/ 解法一&#xff1a;暴力遍历 import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {public static void main(String[] args) {Scanner sca…

使用vscode查bug

具体操作 修改CMakeList.txt # set(CMAKE_BUILD_TYPE "Release")//注释Release模式 set(CMAKE_BUILD_TYPE "Debug")//设置为Debug模式 # set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g")//注释*这行代码是用来设置 CMake 构建系统中 Release 模式…

DS:顺序表的实现(超详细!!)

创作不易&#xff0c;友友们给个三连呗&#xff01; 本文为博主在DS学习阶段的第一篇博客&#xff0c;所以会介绍一下数据结构&#xff0c;并在最后学习对顺序表的实现&#xff0c;在友友们学习数据结构之前&#xff0c;一定要对三个部分的知识——指针、结构体、动态内存管理的…

一、Lamdba 表达式与函数式接口(最终版)

一、Lamdba 表达式与函数式接口 1.1 Lamdba 表达式与函数式接口 1.1.1 Lambda 表达式概述 Lambda 表达式是 Java 8 引入的一个新特性Lambda 表达式可以被视为匿名函数允许在需要函数的地方以更简洁的方法定义功能Lambda 表达式可以完成简洁的函数定义Stream API 中大量使用了…

java 解析word模板(2024-01-25)

本文主要功能是解析word模板 这是一个word解析类&#xff0c;因为我做的系统用到了而且没有可用的帮助类&#xff0c;只能自己写。之前的实现方式是freemarker 模板解析。但是这次要求用poi不在使用freemarker。实现功能比较少&#xff0c;主要是满足开发需求即可&#xff0c;没…

银行数据仓库体系实践(7)--数据模型设计及流程

数据仓库作为全行或全公司的数据中心和总线&#xff0c;汇集了全行各系统以及外部数据&#xff0c;通过良好的系统架构可以保证系统稳定性和处理高效性&#xff0c;那如何保障系统数据的完备性、规范性和统一性呢&#xff1f;这里就需要有良好的数据分区和数据模型&#xff0c;…