leetcode212. 单词搜索 II

news2024/12/19 13:58:28

给定一个 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"]
输出:[]

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 12
  • board[i][j] 是一个小写英文字母
  • 1 <= words.length <= 3 * 104
  • 1 <= words[i].length <= 10
  • words[i] 由小写英文字母组成
  • words 中的所有字符串互不相同

解题分析

问题性质定义
  • 输入:

    1. 一个二维字符网格 board,大小为 m×nm \times n。
    2. 一个单词列表 words
  • 输出:所有可以在网格上找到的单词列表。

  • 限制条件

    1. 单词必须按相邻单元格顺序构成,单元格可以水平或垂直相邻。
    2. 同一单元格内的字母不能重复使用。
    3. 单词列表中的单词互不相同。
  • 边界条件

    1. 空单词列表 words 或空字符网格 board
    2. 网格很大 m,n≤12m, n \leq 12,单词列表很长 ∣words∣≤3×10^4,需要设计高效算法。
    3. 单词可能部分匹配(前缀匹配),但需要完整匹配才能算找到。

解决思路

  1. 算法设计

    • 前缀树 (Trie):构建 Trie 存储单词列表以高效地支持前缀匹配,避免无效搜索。
    • 深度优先搜索 (DFS):从每个网格单元格出发,尝试通过 DFS 查找单词,同时避免重复访问。
    • 剪枝优化
      • 如果当前路径不匹配任何 Trie 前缀,立即停止搜索。
      • 使用标志位标记访问过的单元格。
  2. 时间复杂度分析

    • 构建 Trie:O(L),其中 L 是 words 中所有单词长度总和。
    • 搜索:对于每个单元格,最差情况下搜索整棵 Trie,时间复杂度为 O(m×n×4k),其中 k 是单词的最大长度。
    • 总复杂度:结合 Trie 的剪枝优化,实际效率远优于暴力搜索。
  3. 空间复杂度

    • Trie 的空间复杂度为 O(L)。
    • 递归栈深度为单词的最大长度 O(k)。
  4. C++ 实现
// Trie 节点定义
struct TrieNode {
    unordered_map<char, TrieNode*> children;
    string word = "";  // 保存单词末尾
};

// 构建 Trie
class Trie {
public:
    TrieNode* root;

    Trie() {
        root = new TrieNode();
    }

    void insert(const string& word) {
        TrieNode* node = root;
        for (char c : word) {
            if (!node->children.count(c)) {
                node->children[c] = new TrieNode();
            }
            node = node->children[c];
        }
        node->word = word;  // 在单词结尾保存整个单词
    }
};

class Solution {
public:
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        vector<string> result;
        Trie trie;

        // 1. 将所有单词插入 Trie
        for (const string& word : words) {
            trie.insert(word);
        }

        // 2. 遍历网格,进行深度优先搜索
        int m = board.size(), n = board[0].size();
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                dfs(board, i, j, trie.root, result);
            }
        }

        return result;
    }

private:
    void dfs(vector<vector<char>>& board, int i, int j, TrieNode* node, vector<string>& result) {
        char c = board[i][j];

        // 剪枝:超出边界或当前字符不在 Trie 中
        if (c == '#' || !node->children.count(c)) {
            return;
        }

        node = node->children[c];

        // 如果找到单词,加入结果集
        if (!node->word.empty()) {
            result.push_back(node->word);
            node->word = "";  // 避免重复添加
        }

        // 标记当前单元格为已访问
        board[i][j] = '#';

        // 递归搜索四个方向
        int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        for (auto& dir : directions) {
            int x = i + dir[0], y = j + dir[1];
            if (x >= 0 && x < board.size() && y >= 0 && y < board[0].size()) {
                dfs(board, x, y, node, result);
            }
        }

        // 恢复当前单元格
        board[i][j] = c;

        // 优化:如果当前 Trie 节点无子节点,删除它
        if (node->children.empty()) {
            node = nullptr;
        }
    }
};

代码详细解释

  1. Trie 的构建

    • 使用 Trie 存储单词列表,便于高效的前缀匹配。
    • 每个节点存储其子节点映射和一个 word 字符串,用于标记该节点是否是某个单词的末尾。
  2. DFS 搜索

    • 从网格中每个单元格出发,尝试匹配 Trie 中的单词。
    • 剪枝条件:
      • 当前字符不在 Trie 中。
      • 当前单元格已访问(用 # 标记)。
    • 匹配到单词后,立即加入结果集,并将该单词从 Trie 中删除(防止重复匹配)。
    • 搜索四个方向的邻居单元格。
  3. 优化

    • 标记访问过的单元格,避免重复路径。
    • 当某个 Trie 节点的 children 为空时,提前释放内存。

启发与实际应用

启发:
  • Trie 结合 DFS 是解决字符串匹配问题的经典方法,特别适合大规模、多前缀的单词匹配场景。
  • 剪枝优化和内存管理在搜索问题中至关重要,可以极大提升性能。
实际应用:
  • 拼写检查:在输入法、文本编辑器中高效匹配单词。
  • 词频分析:在网格状数据中统计出现的关键词。
  • 游戏开发:如文字搜索游戏,可以快速匹配玩家找到的单词。
示例场景:

输入法联想

  • 需求:用户输入拼音或字母时,联想出可能的候选词。
  • 实现
    • 使用 Trie 存储所有词典。
    • 用户输入字符时,从 Trie 中按前缀查找所有匹配单词。
    • 联想结果按词频排序,实时显示给用户。

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

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

相关文章

无缝钢管内表面缺陷检测的自强化感知协调网络

摘要 无缝钢管是重要的工业材料。然而&#xff0c;无缝钢管中的内表面缺陷检测具有挑战性&#xff0c;并且会显著影响无缝钢管的性能和寿命。现有的检测方法是劳动强度大的&#xff0c;并且检测结果的可视化程度低。因此&#xff0c;本文提出了一种新型的管道内表面缺陷螺旋式全…

Kioptrix靶场渗透--level1.1

目录 环境搭建 镜像包下载 下载后解压&#xff0c;并修改配置 开始渗透 获取IP 获取具体服务 尝试sql注入 尝试命令执行 反弹shell 提权 查看Linux的内核版本 下载脚本 将脚本上传至靶机 编译脚本 编译后执行 第一个脚本失败&#xff0c;再找个脚本 使用cat /…

AI技术在演示文稿制作中的应用一键生成PPT

在快节奏的现代工作环境中&#xff0c;时间就是金钱。为了提高工作效率&#xff0c;许多专业人士都在寻找能够快速生成演示文稿&#xff08;PPT&#xff09;的工具。本文将探讨AI技术如何帮助用户自动生成演示文稿&#xff0c;从文案撰写到排版&#xff0c;最终输出成品&#x…

中国当代印章孙溟㠭篆刻锤凿印《无题Ⅵ》

孙溟㠭篆刻锤凿印《无题Ⅵ》 孙溟㠭篆刻锤凿印《无题Ⅵ》 此作品有人说看到了流星雨&#xff0c;有人说看到了战争&#xff0c;有人说看到了疾风暴雨&#xff0c;有人说看到了烟花庆新年&#xff0c;有人说是天气突变下的冰雹……&#xff01;至于是什么仁者见仁智者见智。 孙…

游戏引擎学习第54天

仓库: https://gitee.com/mrxiao_com/2d_game 回顾 我们现在正专注于在游戏世界中放置小实体来代表所有的墙。这些实体围绕着世界的每个边缘。我们有活跃的实体&#xff0c;这些实体位于玩家的视野中&#xff0c;频繁更新&#xff0c;而那些离玩家较远的实体则以较低的频率运…

STM32F407ZGT6-UCOSIII笔记2:UCOSIII任务创建实验-Printf 函数卡住 UCOSIII 系统问题解决

今日简单编写熟悉一下UCOSIII系统的任务创建代码&#xff0c;理解一下OS系统&#xff1a; 并发现以及解决了 Printf 函数卡住 UCOSIII 系统问题解决 文章提供测试代码讲解、完整工程下载、测试效果图 目录 文件结构解释&#xff1a; 任务函数文件&#xff1a; 目前各个文件任…

linux centos 7 安装 mongodb7

MongoDB 是一个基于文档的 NoSQL 数据库。 MongoDB 是一个文档型数据库&#xff0c;数据以类似 JSON 的文档形式存储。 MongoDB 的设计理念是为了应对大数据量、高性能和灵活性需求。 MongoDB使用集合&#xff08;Collections&#xff09;来组织文档&#xff08;Documents&a…

kafka的处理的一些问题 消费延迟

kafka的处理的一些问题 消费者客户端不但没有背压而且内存充足&#xff0c;但产生的消费延迟越来越大在Kafka的Leader副本宕机时 消费者客户端不但没有背压而且内存充足&#xff0c;但产生的消费延迟越来越大 比如我们这个kakfa集群一共有3个Broker节点 TOp1有5个分区&#xf…

计算机网络技术基础:3.计算机网络的拓扑结构

网络拓扑结构是指用传输媒体互连各种设备的物理布局&#xff0c;即用什么方式把网络中的计算机等设备连接起来。将工作站、服务站等网络设备抽象为点&#xff0c;称为“节点”&#xff1b;将通信线路抽象为线&#xff0c;称为“链路”。由节点和链路构成的抽象结构就是网络拓扑…

Vue3源码笔记阅读1——Ref响应式原理

本专栏主要用于记录自己的阅读源码的过程,希望能够加深自己学习印象,也欢迎读者可以帮忙完善。接下来每一篇都会从定义、运用两个层面来进行解析 定义 运用 例子:模板中访问ref(1) <template><div>{{str}}</div> </template> <script> impo…

STM32F407寄存器点灯

背景描述&#xff1a; 最近用32开发遇到问题不得不看寄存器了&#xff0c;就回顾了一下寄存器手册的查看方式和寄存器的使用方法&#xff1b; 上一次这么细致的记录还是在刚学习STM32的时候&#xff0c;之前觉得看寄存器手册以及配置寄存器是有点难度的事情&#xff0c;现在回头…

2024年12月11日Github流行趋势

项目名称&#xff1a;maigret 项目维护者&#xff1a;soxoj, kustermariocoding, dependabot, fen0s, cyb3rk0tik项目介绍&#xff1a;通过用户名从数千个站点收集个人档案信息的工具。项目star数&#xff1a;12,055项目fork数&#xff1a;870 项目名称&#xff1a;uv 项目维护…

Halcon中histo_2dim(Operator)算子原理及应用详解

在Halcon中&#xff0c;histo_2dim算子是一个用于计算双通道灰度值图像的直方图的工具。以下是对该算子的原理及应用的详细解释&#xff1a; 一、原理 histo_2dim算子的函数原型为&#xff1a;histo_2dim(Regions, ImageCol, ImageRow : Histo2Dim : : )。 输入参数&#xff…

mysql免安装版配置教程

一、将压缩包解压至你想要放置的文件夹中&#xff0c;注意&#xff1a;绝对路径中要避免出现中文 二、在解压目录下新建my.ini文件&#xff0c;已经有的就直接覆盖 my.ini文件内容 [mysqld] # 设置3306端口 port3306 # 设置mysql的安装目录 basedirD:\\tools\\mysql-8.1.0-win…

(六)- DRM驱动开发(qcom)

一&#xff0c;Linux Android Display 1&#xff0c;Linux Android Display Software Subsystem 密 2&#xff0c;Linux Android Display Architecture 密 二&#xff0c;DRM/KMS Adreno DPU 1&#xff0c;硬件框图 密 1.1 Qualcomm Adreno DPU 8-Series Overview 密 …

手眼标定工具操作文档

1.手眼标定原理介绍 术语介绍 手眼标定&#xff1a;为了获取相机与机器人坐标系之间得位姿转换关系&#xff0c;需要对相机和机器人坐标系进行标定&#xff0c;该标定过程成为手眼标定&#xff0c;用于存储这一组转换关系的文件称为手眼标定文件。 ETH&#xff1a;即Eye To …

CTFshow-文件上传(Web151-170)

CTFshow-文件上传(Web151-170) 参考了CTF show 文件上传篇&#xff08;web151-170&#xff0c;看这一篇就够啦&#xff09;-CSDN博客 Web151 要求png&#xff0c;然后上传带有一句话木马的a.png&#xff0c;burp抓包后改后缀为a.php&#xff0c;然后蚁剑连接&#xff0c;找fl…

基于YOLOv8模型监控视频中的车辆检测与识别应用

1.摘要 该项目旨在通过技术手段加强交通纪律&#xff0c;提供一种更为人性化和智能化的交通监控方法。具体而言&#xff0c;通过利用PyQt5、YOLOv8和TensorFlow等技术栈&#xff0c;实现了对车辆的高效检测与识别&#xff0c;主要实现车辆类型识别以及速度监测等功能&#xff0…

CISC RISC

CISC&#xff1a;设计目标是通过复杂的指令来提高代码密度&#xff0c;减少指令数量&#xff0c;适合内存资源较为有限的系统。CISC处理器的硬件复杂度较高&#xff0c;但在某些应用场合&#xff08;如桌面计算机&#xff09;能够提供足够的性能。 RISC&#xff1a;设计目标是…

AI Agent与MEME:技术与文化融合驱动Web3创新

AI Agent如何引领Web3新时代&#xff1f; 随着Web3与区块链技术的迅速发展&#xff0c;AI Agent作为人工智能与区块链的交汇点&#xff0c;正在逐步成为推动去中心化生态的重要力量。同时&#xff0c;MEME文化凭借其强大的社区驱动力和文化渗透力&#xff0c;在链上生态中扮演着…