【LeetCode】单词搜索 II [H](前缀树)

news2024/11/15 17:29:09

212. 单词搜索 II - 力扣(LeetCode)

一、题目

给定一个 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 中的所有字符串互不相同

二、代码

class Solution {
    public List<String> findWords(char[][] board, String[] words) {
        // 记录前缀树中的所有字符串,做去重
        HashSet<String> trieSet = new HashSet<>();
        // 前缀树的根节点
        TrieNode head = new TrieNode();
        // 利用单词表构造前缀树
        for (String word : words) {
            // 相同的单词就去重
            if (!trieSet.contains(word)) {
                addTrieNode(head, word);
                trieSet.add(word);
            }
        }

        // 记录递归轨迹中走过的字符
        LinkedList<Character> path = new LinkedList<>();
        // 要返回的答案
        List<String> ans = new ArrayList<>();
        // 尝试以每一个位置作为起始点,看看能不能找到一个单词能和前缀树中的单词匹配上
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                process(board, i, j, head, path, ans);
            }
        }

        return ans;
    }
    
    public int process(char[][] board, int row, int col, TrieNode node, LinkedList<Character> path, List<String> ans) {
        // 如果矩阵中该位置为0,说明该位置已经走过了,不要重复走了,直接返回0
        if (board[row][col] == 0 ) {
            return 0;
        }

        char cha = board[row][col];
        // 如果当前矩阵中的字符在前缀树中没有相应的路线,也返回0
        if (node.next[cha - 'a'] == null || node.next[cha - 'a'].pass == 0) {
            return 0;
        }

        TrieNode next = node.next[cha - 'a'];
        board[row][col] = 0;
        path.addLast(cha);
        int cnt = 0;

        // 如果来到了一个单词的结尾字符,就说明找到了一个单词,将该单词加入到ans中
        if (next.end == true) {
            ans.add(charListToString(path));
            next.end = false;
            cnt++;
        }

        // 开始尝试四个方向,并且需要保证不越界
        if (row + 1 < board.length) {
            //node.pass--;
            cnt += process(board, row + 1, col, next, path, ans);
        }

        if (row - 1 >= 0) {
          
            //node.pass--;
            cnt += process(board, row - 1, col, next, path, ans);
            
        }

        if (col + 1 < board[0].length) {
            //node.pass--;
            cnt += process(board, row, col + 1, next, path, ans);
                
        }

        if (col - 1 >= 0) {
            //node.pass--;
            cnt += process(board, row, col - 1, next, path, ans);
        }

        // 恢复现场
        board[row][col] = cha;
        path.pollLast();
        next.pass -= cnt;
        return cnt;
    }

    // 将字符List转换为String
    public String charListToString(LinkedList<Character> path) {
        StringBuilder sb = new StringBuilder();

        for (Character c : path) {
            sb.append(c);
        }

        return sb.toString();
    }

    // 前缀树节点类
    class TrieNode {
        public TrieNode[] next;
        // 记录该节点被不同单词通过的次数
        public int pass;
        // 该节点是否为单词结束位置
        public boolean end;

        public TrieNode() {
            next = new TrieNode[26];
            pass = 0;
            end = false;
        }
    }

    // 将word加入前缀树
    public void addTrieNode(TrieNode head, String word) {
        char[] w = word.toCharArray();

        TrieNode node = head;
        node.pass++;
        for (int i = 0; i < w.length; i++) {
            if (node.next[w[i] - 'a'] == null) {
                node.next[w[i] - 'a'] = new TrieNode();
            }
            // node向下移动一个位置
            node = node.next[w[i] - 'a'];
            // 将下面的node的pass也加1
            node.pass++;
            
        }
        // 标记单词结尾节点
        node.end = true;
    }
}

三、解题思路 

尝试以矩阵中每个点作为出发点,收集单词。先将单词表中的所有单词建成前缀树,这样可以加快匹配速度。只要是涉及到字符串匹配的,马上要想到可以利用前缀树优化。

来到某一个(i,j)位置的字符a,先看前缀树的头节点的直接子路线有没有a,发现有a,那么就说明可以从a开始找。如果没有a就直接跳过,去尝试以下一个位置的字符作为开始点查找。

然后从a开始可以往上下左右4个方向走,至于到底有没有必要往某个方向走,也可以用前缀树来指导。例如我们如果发现a的直接子路线没有a在矩阵中上下左右的字符,那么就没有方向可以走。

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

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

相关文章

QT系列第2节 QT中元对象系统

QT是在标准C上进行了扩展&#xff0c;所以就有自己的特性&#xff0c;其中元对象系统就是其一。元对象系统有点类似于java和go语言中的反射&#xff0c;让我们在编程时解决问题多了些方法和思路&#xff0c;关于元对象可以简单总结出以下内容项。 目录 一.元对象要点总结 二…

Linux转发性能评估与优化之——转发瓶颈分析与解决方案

线速问题 很多人对这个线速概念存在误解。认为所谓线速能力就是路由器/交换机就像一根网线一样。而这&#xff0c;是不可能的。应该考虑到的一个概念就是延迟。数据包进入路由器或者交换机&#xff0c;存在一个核心延迟操作&#xff0c;这就是选路&#xff0c;对于路由器而言&…

软件工程复习简略

软件工程复习简略1.什么是软件生存周期&#xff1f;通常可划分为哪些阶段&#xff1f;2.简述需求分析要经过哪些步骤&#xff0c;每个步骤的作用。3.详细设计有哪些常用工具&#xff1f;&#xff08;注意Pad图的画法&#xff09;4.软件测试的目的和原则是什么&#xff1f;5.测试…

pythonselenium自动化测试实战项目(完整、全面)

前言 之前的文章说过&#xff0c; 要写一篇自动化实战的文章&#xff0c; 这段时间比较忙再加回家过11一直没有更新博客&#xff0c;今天整理一下实战项目的代码共大家学习。&#xff08;注:项目是针对我们公司内部系统的测试&#xff0c;只能内部网络访问&#xff0c;外部网络…

【SpringCloud负载均衡】【源码+图解】【三】LoadBalancer的工作原理

【SpringCloud负载均衡】【源码图解】【二】LoadBalancer配置 目录3. LoadBalancer的工作原理3.1 创建LoadBalancerRequest3.2 创建上下文3.2.1 properties3.2.2 configurations3.2.3 contexts3.3 获取ReactiveLoadBalancer3.4 获取ServiceInstance3.5 向serviceInstance请求结…

Java要学到什么程度才可以找工作?

Java为不同的集合提供了一个集合框架。集合基于数据结构&#xff0c;比如常见的&#xff1a;列表、数组、集合、哈希图等等。因此&#xff0c;在研究集合时&#xff0c;最好了解一点数据结构的相关知识。 主要副题&#xff1a; List Set Map ArrayList LinkedList Queue…

web3:智能合约-虚拟机(EVM、HVM、WASM、MOVE)

在区块链上&#xff0c;用户通过运行部署在区块链上的合约&#xff0c;完成需要共识的操作。而为智能合约提供运行环境的便是对应的虚拟机。 目录EVM基础概念技术细节EVM的存储模型交易在EVM的执行普通转账交易智能合约的创建或者调用EVM机器语言与现有的虚拟机科技作比较EVM的…

Java中类的复用

类的复用&#xff08;组合与继承&#xff09; 第一种方法&#xff1a;只需在新类中产生现有类的对象&#xff0c;新类由现有类组成&#xff0c;也称为组合&#xff0c;该方法只是复用了现有程序代码的功能&#xff1b; 第二种方法&#xff1a;按现有类来创建新类&#xff0c;…

m基于LPF-VMD和KELM的鸟群优化算法的风速预测算法matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 1).使用 LPF-VMD 对风速时间序列进行分解&#xff0c; 得到一个低频的趋势分量以及 n 个由 VMD 分解得 到的 BIMF。 2).对 LPF-VMD 分解得到的各分量分别建立 KELM 预测模型&#xff0c;采用 B…

【进阶】C语言第二课:升级你的指针(1)

目录 &#x1f929;前言&#x1f929;&#xff1a; 一、字符指针&#x1f92f;&#xff1a; 1.字符指针的使用&#x1f99d;&#xff1a; 2.常量字符串&#x1f98a;&#xff1a; 3.相关面试题分析&#x1f423;&#xff1a; 二、指针数组&#x1f9d0;&#xff1a; 三、数…

vue+nodejs公益图书借阅捐赠管理系统

公益图书捐赠管理系统 用户信息&#xff1a;id、用户名、密码、捐书数量&#xff08;管理员端可以点击跳转查看详情&#xff09;、上传电子书数量&#xff08;管理员端可以点击跳转查看详情&#xff09;、借阅图书数量&#xff08;管理员端可以点击跳转查看详情&#xff09;&am…

利用全长转录组多重阵列测序检测同源异构体

哈佛大学和麻省理工学院近期发表了“High-throughput RNA isoform sequencing using programmable cDNA concatenation.”研究论文中&#xff0c;将 cDNA 串联成可用于长读长测序最佳的单分子的技术应用于肿瘤浸润 T 细胞的单细胞 RNA 测序,提高了寻找可变剪接基因的准确度&…

秒懂:JCTool 的 Mpsc 高性能无锁队列 (史上最全+10W字长文)

文章很长&#xff0c;而且持续更新&#xff0c;建议收藏起来&#xff0c;慢慢读&#xff01;疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 &#xff1a; 免费赠送 :《尼恩Java面试宝典》 持续更新 史上最全 面试必备 2000页 面试必备 大厂必备 涨薪必备 免费赠送 经典…

C# 修饰符

一 访问控制符 二 static 1 static 的字段、方法、属性是属于整个类的 ① static方法中&#xff0c;不能访问实例变量&#xff1b; ② 调用static方法时&#xff0c;直接用类名访问 Console.Write();Math.Sqrt(); Convert.ToDateTime();DateTime.Parse String.Copy(a);Strin…

【JavaSE】javaSE练习项目——>《简易图书管理系统》

目录 前言&#xff1a; 1、项目实现要求 2、设计思路流程 设计思路&#xff1a; 登录后菜单的实现效果&#xff1a; 3、代码实现&#xff08;大体框架&#xff09; Main类 book包 Book类 BookList类 user包 User类 AdminUser(管理员)类 NormalUser&#xff08;普通…

Hibernate Validator 使用详解

目录 Hibernate Validator的依赖 Hibernate Validator 支持注解 空与非空检查 Boolean值检查 日期检查 数值检查 其他 Hibernate-validator扩展约束 Hibernate Validator 校验 简单对象校验 嵌套对象校验 Hibernate Validator 分组校验 静态分组 动态分组 动态分…

【软件测试】刚入职后,快速适应新的工作需要做啥?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 入职一家新公司后&a…

基于AD8226的环境光传感器电路

人们越来越多地认为环境光是一种能源,可用于驱动心率监控器、浴室灯具、远程天气传感器和其他低功耗器件。对于能量采集系统,最关键的是精确测量环境光的能力。本设计思路将描述一种简单的低成本电路,可以根据环境光的强度按一定比例提供电压。 所用传感器是一款光敏电阻(L…

spring framework 容器

org.springframework.beans 和 org.springframework.context 包是 Spring Framework 的 IoC 容器的基础。 这里需掌握两个体系结构&#xff0c;BeanFactory 和 ApplicationContext。 BeanFactory 主要接口&#xff0c;可分为三级&#xff1a; BeanFactory 是顶层容器&#xf…

Nacos 配置中心之主动拉取

客户端 客户端的配置有两种方式来维持,一是客户端主动拉取,而是客户端长轮询更新 配置文件的种类 1、本地配置文件: 本地就已经存在的配置文件 2、 本地缓存文件: 从服务端获取的保存在了本地 (本地生成了文件) 3、 cacheData 缓存数据: 内存中缓存的配置文件数据 客户端主动获…