【面试高频题】难度 3/5,字典树热门运用题
题目描述
这是 LeetCode 上的 「745. 前缀和后缀搜索」 ,难度为 「困难」。
Tag : 「字典树」
设计一个包含一些单词的特殊词典,并能够通过前缀和后缀来检索单词。
实现 WordFilter 类:
-
WordFilter(string[] words)使用词典中的单词words初始化对象。 -
f(string pref, string suff)返回词典中具有前缀prefix和后缀suff的单词的下标。如果存在不止一个满足要求的下标,返回其中 最大的下标 。如果不存在这样的单词,返回 。
示例:
输入
["WordFilter", "f"]
[[["apple"]], ["a", "e"]]
输出
[null, 0]
解释
WordFilter wordFilter = new WordFilter(["apple"]);
wordFilter.f("a", "e"); // 返回 0 ,因为下标为 0 的单词:前缀 prefix = "a" 且 后缀 suff = "e" 。
提示:
-
-
-
-
words[i]、pref和suff仅由小写英文字母组成 -
最多对函数 f执行 次调用
基本分析
为了方便,我们令 words 为 ss,令 pref 和 suff 分别为 a 和 b。
搜索某个前缀(后缀可看做是反方向的前缀)容易想到字典树,但单词长度数据范围只有 ,十分具有迷惑性,使用暴力做法最坏情况下会扫描所有的 ,不考虑任何的剪枝操作的话,计算量也才为 ,按道理是完全可以过的。
但不要忘记 LC 是一个具有「设定每个样例时长,同时又有总时长」这样奇怪机制的 OJ。
暴力(TLE or 双百)
于是有了 Java 总时间超时,TypeScripe 双百的结果(应该是 TypeScript 提交不多,同时设限宽松的原因):
Java 代码:
class WordFilter {
String[] ss;
public WordFilter(String[] words) {
ss = words;
}
public int f(String a, String b) {
int n = a.length(), m = b.length();
for (int k = ss.length - 1; k >= 0; k--) {
String cur = ss[k];
int len = cur.length();
if (len < n || len < m) continue;
boolean ok = true;
for (int i = 0; i < n && ok; i++) {
if (cur.charAt(i) != a.charAt(i)) ok = false;
}
for (int i = 0; i < m && ok; i++) {
if (cur.charAt(len - 1 - i) != b.charAt(m - 1 - i)) ok = false;
}
if (ok) return k;
}
return -1;
}
}
TypeScript 代码:
class WordFilter {
ss: string[]
constructor(words: string[]) {
this.ss = words
}
f(a: string, b: string): number {
const n = a.length, m = b.length
for (let k = this.ss.length - 1; k >= 0; k--) {
const cur = this.ss[k]
const len = cur.length
if (len < n || len < m) continue
let ok = true
for (let i = 0; i < n && ok; i++) {
if (cur[i] != a[i]) ok = false
}
for (let i = m - 1; i >= 0; i--) {
if (cur[len - 1 - i] != b[m - 1 - i]) ok = false
}
if (ok) return k
}
return -1
}
}
-
时间复杂度:初始化操作复杂度为 ,检索操作复杂度为 -
空间复杂度:
Trie
使用字典树优化检索过程也是容易的,分别使用两棵 Trie 树来记录 的前后缀,即正着存到 tr1 中,反着存到 Tr2 中。
❝还不了解
❞Trie的同学可以先看前置 🧀:实现 Trie (前缀树) 前置 🧀 通过图解形式讲解了Trie的结构与原理,以及提供了两种实现Trie的方式
同时对于字典树的每个节点,我们使用数组 idxs 记录经过该节点的字符串 所在 ss 中的下标 ,若某个字典树节点的索引数组 tr.idxs 为 则代表「从根节点到 tr 节点所对应的字符串」为 的前缀。
这样我们可以即可在扫描前后缀 a 和 b 时,得到对应的候选下标列表 l1 和 l2,由于我们将 添加到两棵 tr 中是按照下标「从小到大」进行,因此我们使用「双指针」算法分别从 l1 和 l2 结尾往后找到第一个共同元素即是答案(满足条件的最大下标)。
❝使用
❞Trie优化后,Java从TLE到AC,TypeScript耗时为原本的 :
Java 代码:
class WordFilter {
class TrieNode {
TrieNode[] tns = new TrieNode[26];
List<Integer> idxs = new ArrayList<>();
}
void add(TrieNode p, String s, int idx, boolean isTurn) {
int n = s.length();
p.idxs.add(idx);
for (int i = isTurn ? n - 1 : 0; i >= 0 && i < n; i += isTurn ? -1 : 1) {
int u = s.charAt(i) - 'a';
if (p.tns[u] == null) p.tns[u] = new TrieNode();
p = p.tns[u];
p.idxs.add(idx);
}
}
int query(String a, String b) {
int n = a.length(), m = b.length();
TrieNode p = tr1;
for (int i = 0; i < n; i++) {
int u = a.charAt(i) - 'a';
if (p.tns[u] == null) return -1;
p = p.tns[u];
}
List<Integer> l1 = p.idxs;
p = tr2;
for (int i = m - 1; i >= 0; i--) {
int u = b.charAt(i) - 'a';
if (p.tns[u] == null) return -1;
p = p.tns[u];
}
List<Integer> l2 = p.idxs;
n = l1.size(); m = l2.size();
for (int i = n - 1, j = m - 1; i >= 0 && j >= 0; ) {
if (l1.get(i) > l2.get(j)) i--;
else if (l1.get(i) < l2.get(j)) j--;
else return l1.get(i);
}
return -1;
}
TrieNode tr1 = new TrieNode(), tr2 = new TrieNode();
public WordFilter(String[] ss) {
int n = ss.length;
for (int i = 0; i < n; i++) {
add(tr1, ss[i], i, false);
add(tr2, ss[i], i, true);
}
}
public int f(String a, String b) {
return query(a, b);
}
}
TypeScript 代码:
class TrieNode {
tns: TrieNode[] = new Array<TrieNode>()
idxs: number[] = new Array<number>()
}
class WordFilter {
add(p: TrieNode, s: string, idx: number, isTurn: boolean): void {
const n = s.length
p.idxs.push(idx)
for (let i = isTurn ? n - 1 : 0; i >= 0 && i < n; i += isTurn ? -1 : 1) {
const u = s.charCodeAt(i) - 'a'.charCodeAt(0)
if (p.tns[u] == null) p.tns[u] = new TrieNode()
p = p.tns[u]
p.idxs.push(idx)
}
}
query(a: string, b: string): number {
let n = a.length, m = b.length
let p = this.tr1
for (let i = 0; i < n; i++) {
const u = a.charCodeAt(i) - 'a'.charCodeAt(0)
if (p.tns[u] == null) return -1
p = p.tns[u]
}
const l1 = p.idxs
p = this.tr2
for (let i = m - 1; i >= 0; i--) {
const u = b.charCodeAt(i) - 'a'.charCodeAt(0)
if (p.tns[u] == null) return -1
p = p.tns[u]
}
const l2 = p.idxs
n = l1.length; m = l2.length
for (let i = n - 1, j = m - 1; i >= 0 && j >= 0; ) {
if (l1[i] < l2[j]) j--
else if (l1[i] > l2[j]) i--
else return l1[i]
}
return -1
}
tr1: TrieNode = new TrieNode()
tr2: TrieNode = new TrieNode()
constructor(ss: string[]) {
for (let i = 0; i < ss.length; i++) {
this.add(this.tr1, ss[i], i, false)
this.add(this.tr2, ss[i], i, true)
}
}
f(a: string, b: string): number {
return this.query(a, b)
}
}
C++ 代码:
class WordFilter {
public:
struct TrieNode {
TrieNode* tns[26] {nullptr};
vector<int> idxs;
};
void add(TrieNode* p, const string& s, int idx, bool isTurn) {
int n = s.size();
p->idxs.push_back(idx);
for(int i = isTurn ? n - 1 : 0; i >= 0 && i < n; i += isTurn ? -1 : 1) {
int u = s[i] - 'a';
if(p->tns[u] == nullptr) p->tns[u] = new TrieNode();
p = p->tns[u];
p->idxs.push_back(idx);
}
}
int query(const string& a, const string& b) {
int n = a.size(), m = b.size();
auto p = tr1;
for(int i = 0; i < n; i++) {
int u = a[i] - 'a';
if(p->tns[u] == nullptr) return -1;
p = p->tns[u];
}
vector<int>& l1 = p->idxs;
p = tr2;
for(int i = m - 1; i >= 0; i--) {
int u = b[i] - 'a';
if(p->tns[u] == nullptr) return -1;
p = p->tns[u];
}
vector<int>& l2 = p->idxs;
n = l1.size(), m = l2.size();
for(int i = n - 1, j = m - 1; i >= 0 && j >= 0; ) {
if(l1[i] > l2[j]) i--;
else if(l1[i] < l2[j]) j--;
else return l1[i];
}
return -1;
}
TrieNode* tr1 = new TrieNode, *tr2 = new TrieNode;
WordFilter(vector<string>& ss) {
int n = ss.size();
for(int i = 0; i < n; i++) {
add(tr1, ss[i], i, false);
add(tr2, ss[i], i, true);
}
}
int f(string a, string b) {
return query(a, b);
}
};
-
时间复杂度:初始化操作复杂度为 ,检索过程复杂度为 ,其中 为前后缀的最大长度, 为初始化数组长度,代表最多有 个候选下标(注意:这里的 只是粗略分析,实际上如果候选集长度越大的话,说明两个候选集是重合度是越高的,从后往前找的过程是越快结束的,可以通过方程算出一个双指针的理论最大比较次数 ,如果要将 卡满成 的话,需要将两个候选集设计成交替下标,此时 f如果仍是 次调用的话,必然会面临大量的重复查询,可通过引入map记录查询次数来进行优化) -
空间复杂度:
最后
这是我们「刷穿 LeetCode」系列文章的第 No.745 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉
相关文章:
【面试高频题】难度 3/5,字典树热门运用题
题目描述 这是 LeetCode 上的 「745. 前缀和后缀搜索」 ,难度为 「困难」。 Tag : 「字典树」 设计一个包含一些单词的特殊词典,并能够通过前缀和后缀来检索单词。 实现 WordFilter 类: WordFilter(string[] words) 使用词典中的单词 words 初…...
vue base64图片转file流 下载到本地 或者上传
<img :src"data:image/png;base64,form.img" style"max-width:280px;max-height: 280px;margin: auto;" />// base64 转file const base64ToFile()>{let byImg atob(form.img); // 解码base64let n byImg.lengthlet a new Uint8Array(n);while…...
无涯教程-PHP - 简介
PHP 7是最期待的,它是PHP编程语言的主要功能版本。 PHP 7于2015年12月3日发布。本教程将以简单直观的方式教您PHP 7的新功能及其用法。 无涯教程假设您已经了解旧版本的PHP,现在就可以开始学习PHP 7的新功能。 使用下面的示例- <html><head&…...
web基础+HTTP协议+httpd详细配置
目目录录 一、Web基础1.1 HTML概述1.1.1 HTML的文件结构1.1.2 HTML中的部分基本标签 1.3 MIME1.4 URI 和 URL1.4 定义1.4.2 URI 和 URL 的区别 二、静态资源和动态资源2.1 静态资源2.2 动态资源 三、HTTP协议3.1 HTTP协议简介3.2 HTTP协议版本3.2 HTTP方法3.3 HTTP请求访问的完…...
【sql】MongoDB的增删改查分页条件等
【sql】MongoDB的增删改查分页条件等 //增 //新增数据2种方式 db.msg.save({"name":"springboot😀"}); db.msg.insert({"name":"mango good"}); db.msg.save({"name":"springboot",type:"工具书&…...
我的动态归纳(便于搜索)
linux dns配置文件是“/etc/resolv.conf”,该配置文件用于配置DNS客户,它包含了主机的域名搜索顺序和DNS/服务器的地址,每一行包括一个关键字和一个或多个空格隔开的参数。 /etc/resolv.conf (不配置就不能域名解析) 可…...
langchain ChatGPT AI私有知识库
企业知识库 原理就是把文档变为向量数据库,然后搜索向量数据库,把相似的数据和问题作为prompt, 输入到大模型,再利用GPT强大的自然语言处理、推理和分析等方面的能力将答案返回给用户 什么是langchain? langchain是一个强大的…...
API接口常用数据格式Json,Json的定义和XML的区别
现在程序员还有谁不知道 JSON 吗?无论对于前端还是后端,JSON 都是一种常见的数据格式。那么 JSON 到底是什么呢? JSON 的定义 JSON (JavaScript Object Notation) ,是一种轻量级的数据交换格式。它的使用…...
密码学学习笔记(二十一):SHA-256与HMAC、NMAC、KMAC
SHA-256 SHA-2是广泛应用的哈希函数,并且有不同的版本,这篇博客主要介绍SHA-256。 SHA-256算法满足了哈希函数的三个安全属性: 抗第一原像性 - 无法根据哈希函数的输出恢复其对应的输入。抗第二原像性 - 给定一个输入和它的哈希值…...
操作系统-笔记-第四章-文件管理
目录 四、第四章——文件管理 1、文件管理——基础概念 (1)文件结构 (2)操作系统提供的接口 (3)总结 2、文件的逻辑结构 (1)有结构文件(类似SQL表文件)…...
【MiniGUI】文字颜色实现透明度变化
在MiniGUi中,输出文字时有时候希望文字带有透明度信息, 即文字能够透出下面的图像来。 很自然地想到,设置颜色时,将颜色设置为带有透明度的颜色: SelectFont(hdc, mg_font);SetTextColor(hdc, RGBA2Pixel(HDC_SCREEN, …...
css中元素加定位之后到一定距离元素会变小
css中元素加定位之后到一定距离元素会变小 主要原因:元素没有加宽高 .swiperWrapper .active{bottom: 380px;left: 215px;z-index: 10; } .swiperWrapper .next{bottom: 170px;left: 7%;z-index: 20; } .swiperWrapper .prev{bottom: 360px;left: 0%;z-index: 30;…...
Java 语言实现冒泡排序
Java 语言实现冒泡排序 介绍 冒泡排序是一种简单直观的排序算法,它重复地比较相邻的两个元素,如果顺序错误就交换它们,直到没有需要交换的元素为止。冒泡排序的思路是通过每一轮的比较将最大(或最小)的元素逐渐“冒泡…...
面向对象单选题
下列选项中不属于面向对象的特征的是(B) A、封装性 B、安全性 C、继承性 D、多态性 在Java中,关于继承,类只支持(A) A、单继承 B、多继承 C、两个都可以 D、两个都不可以 用于定义成员的访问控制权的一组关键字…...
微服务-Fegin
在之前我们两服务之间调用的时候用的是restTemplate,但是这个方式调用存在很多的问题 String url "http://userservice/user/" order.getUserId(); 代码可读性差,编码体验不统一参数复杂的url难以维护 所以我们大力推出我们今天的主角--Fegin Feign是…...
[oneAPI] 使用字符级 RNN 生成名称
[oneAPI] 使用字符级 RNN 生成名称 oneAPI特殊写法使用字符级 RNN 生成名称Intel Optimization for PyTorch数据下载加载数据并对数据进行处理创建网络训练过程准备训练训练网络 结果 参考资料 比赛:https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0517…...
【ROS】参数服务器--理论模型与参数操作(C++)
一、概念介绍 参数服务器在ROS中主要用于实现不同节点之间的数据共享。参数服务器相当于是独立于所有节点的一个公共容器,可以将数据存储在该容器中,被不同的节点调用,当然不同的节点也可以往其中存储数据。 作用:存储一些多节点…...
[oneAPI] 基于BERT预训练模型的英文文本蕴含任务
[oneAPI] 基于BERT预训练模型的英文文本蕴含任务 Intel DevCloud for oneAPI 和 Intel Optimization for PyTorch基于BERT预训练模型的英文文本蕴含任务语料介绍数据集构建 模型训练 结果参考资料 比赛:https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0…...
【洛谷】P1163 银行贷款
原题链接:https://www.luogu.com.cn/problem/P1163 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 这题需要注意的是利率按月累计这句话,也就是相当于“利滚利”。 我们定义sum变量表示贷款原值,money表示每月支付…...
Java版工程行业管理系统源码-专业的工程管理软件-提供一站式服务 em
鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展,企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性,公司对内部工…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
