【面試高頻題】難度 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]prefsuff 僅由小寫英文字母組成
  • 最多對函數 f 執行 次調用

基本分析

為了方便,我們令 wordsss,令 prefsuff 分別為 ab

搜索某個前綴(后綴可看做是反方向的前綴)容易想到字典樹,但單詞長度數據范圍只有 ,十分具有迷惑性,使用暴力做法最壞情況下會掃描所有的 ,不考慮任何的剪枝操作的話,計算量也才為 ,按道理是完全可以過的。

但不要忘記 LC 是一個具有「設定每個樣例時長,同時又有總時長」這樣奇怪機制的 OJ。

暴力(TLE or 雙百)

于是有了 Java 總時間超時,TypeScripe 雙百的結果(應該是 TypeScript 提交不多,同時設限寬松的原因):

alt

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 節點所對應的字符串」為 的前綴。

這樣我們可以即可在掃描前后綴 ab 時,得到對應的候選下標列表 l1l2,由于我們將 添加到兩棵 tr 中是按照下標「從小到大」進行,因此我們使用「雙指針」算法分別從 l1l2 結尾往后找到第一個共同元素即是答案(滿足條件的最大下標)。

?

使用 Trie 優化后,JavaTLEACTypeScript 耗時為原本的

?
alt

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 原題鏈接和其他優選題解。

更多更全更熱門的「筆試/面試」相關資料可訪問排版精美的 合集新基地 🎉🎉

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/41463.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/41463.shtml
英文地址,請注明出處:http://en.pswp.cn/news/41463.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

單片機之從C語言基礎到專家編程 - 4 C語言基礎 - 4.9 變量與常量

基本數據類型可以作為變量與常量使用,顧名思義&#xff0c;變量運行時可以改變其值&#xff0c;常量運行時不會改變其值。 常量分為整型常量、浮點型常量、字符常量、字符串常量和符號常量。 通常用#define來定義一個標識符來表示一個常量 用type name 常量來定義一個變量,…

無法將“環境變量”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱(pycharm)

無法將“配置的任何一個環境變量”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱。 記錄解決“無法將“C:......conda.exe”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱”以及“表達式或語句中包含意外的標記”的系列問題(VSCode開發環境)一、Conda.exe無法正常識…

ROS2 學習(三)話題

話題 節點之間的通信。 叫話題很形象。發布者發布一定數據類型的話題&#xff0c;訂閱者訂閱發布者。 訂閱者發布者不唯一。 異步通信&#xff0c;適用于周期發布的數據而不是邏輯性強的數據。 .msg 格式的消息結構&#xff0c;一種通信接口。 每個話題 topic 有話題名&a…

【Java高級開發高頻面試題】面試者角度的口述版

文章目錄 1.具備扎實的Java基礎集合HashMap底層工作原理HashMap版本問題HashMap并發修改異常HashMap影響HashMap性能的因素HashMap使用優化 SynchronizedThreadLocalAQS線程池JVM內存模型類加載機制與雙親委派垃圾回收算法、垃圾回收器、空間分配擔保策略引用計數器算法、可達性…

創建 Web 內容目錄

創建 Web 內容目錄 按照下方所述&#xff0c;創建一個名為 /home/curtis/ansible/webcontent.yml 的 playbook &#xff1a; 該 playbook 在 dev 主機組中的受管節點上運行 創建符合下列要求的目錄 /webdev &#xff1a; 所有者為 webdev 組 具有常規權限&#xff1a;ownerread…

Nginx反向代理

目錄 一.簡介1.反向代理 二.案例1.案例12.案例2 一.簡介 1.反向代理 1.1反向代理&#xff1a; 是指代理服務器來接收Internet上的客戶端請求&#xff0c;然后將請求轉發給內部網絡上的服務器&#xff0c;并將從服務器上得到的結果返回給客戶端。此時代理服務器對外就表現為一…

循環隊列的實現(c語言)

前言 循環隊列是隊列的一種特殊的結構&#xff0c;在生產者——消費者模型中常常使用它&#xff0c; 它在邏輯上是一個環形的連續的結構。在物理可以使用數組來實現。 目錄 1.循環隊列的邏輯結構 2.空的循環隊列和滿的循環隊列 3.循環隊列插入和刪除 4.代碼實現 …

淺談Redis的maxmemory設置以及淘汰策略

推薦閱讀 AI文本 OCR識別最佳實踐 AI Gamma一鍵生成PPT工具直達鏈接 玩轉cloud Studio 在線編碼神器 玩轉 GPU AI繪畫、AI講話、翻譯,GPU點亮AI想象空間 資源分享 「java、python面試題」來自UC網盤app分享&#xff0c;打開手機app&#xff0c;額外獲得1T空間 https://dr…

音視頻實時通話解決方案

1、問題提出 想要實現音視頻通話,對于大部分人可能會覺得很難,但是實際上,有些事情并沒有大家想的那樣困難,只要功夫深,鐵杵磨成針。 機緣巧合下,在業務中,我也遇到了一個業務場景需要實現音視頻通話,我們不可能自己從零開始干,我本次用到的核心是WebRTC。 2、WebRT…

治療偏頭痛等亞疼痛的遠程電神經調控(REN)設備

原文鏈接&#xff1a; NERIVIO CE標志適應癥擴展到青少年和成人偏頭痛的預防和急性治療 (prnewswire.com) 公司官網&#xff1a; Homepage - Theranica APP下載鏈接&#xff1a; Migraine Headache Treatment - Nerivio 使用過程問題&#xff1a; 常見問題 - 無藥物偏頭痛兩…

統計XML標注文件中各標注類別的標簽數量

目標檢測任務重&#xff0c;擔心數據集中各標簽類別不均衡&#xff0c;想統計XML標注文件中各標注類別的標簽數量&#xff0c;可以使用以下腳本&#xff1a; import os import glob import xml.etree.ElementTree as etdef count_labels(source_dir):file_list glob.glob(os.…

Flink狀態和狀態管理

1.什么是狀態 官方定義&#xff1a;當前計算流程需要依賴到之前計算的結果&#xff0c;那么之前計算的結果就是狀態。 這句話還是挺好理解的&#xff0c;狀態不只存在于Flink&#xff0c;也存在生活的方方面面&#xff0c;比如看到一個認識的人&#xff0c;如何識別認識呢&am…

神經網絡基礎-神經網絡補充概念-24-隨機初始化

由來 在神經網絡的訓練過程中&#xff0c;權重和偏差的初始值對模型的性能和訓練過程的收斂速度都有影響。隨機初始化是一種常用的權重和偏差初始值設置方法&#xff0c;它有助于打破對稱性&#xff0c;避免網絡陷入局部最優解。 概念 當所有權重和偏差都被設置為相同的初始…

Python Web框架:Django、Flask和FastAPI巔峰對決

今天&#xff0c;我們將深入探討Python Web框架的三巨頭&#xff1a;Django、Flask和FastAPI。無論你是Python小白還是老司機&#xff0c;本文都會為你解惑&#xff0c;帶你領略這三者的魅力。廢話不多說&#xff0c;讓我們開始這場終極對比&#xff01; Django&#xff1a;百…

web基礎入門和php語言基礎入門 二

web基礎入門和php語言基礎入門 二 MySQL入門-續MySQL之數據查詢操作MySQL其他知識點 php語言基礎入門認識PHPPHP的工作流程安裝PHP環境認識一個PHP程序PHP基礎知識點進入正題 PHP與WEB交互PHP與MySQL交互總結 MySQL入門-續 MySQL之數據查詢操作 WHERE 子句&#xff0c;條件限…

# 快速評估立功科技基于S32K324的TMS方案

文章目錄 1.前言2.立功科技的TMS方案介紹2.1 介紹資料2.2 簡要介紹 3.S32K3_TriMotor評估板測試3.1 環境搭建S32 Design Studio for S32 Platform 3.4安裝RTD 2.0.0安裝Freemaster 3.2 3.2 例程測試3.3 例程適配3.4 雙核燒錄3.5 測試 1.前言 最近和一些做汽車水泵/風機的客戶交…

算法概述-Java常用算法

算法概述-Java常用算法 1、算法概念2、算法相關概念3、算法的性能評價4、算法應用歸納 1、算法概念 廣泛算法定義&#xff1a;算法是模型分析的一組可行性的、確定的和有窮的規則。 經典算法特征&#xff1a;有窮性、確切性、輸入、輸出和可行性。 常用的算法包括遞推、遞歸、窮…

maven如何建立JavaWeb項目并連接數據庫,驗證登錄

這里是建立建立web項目&#xff1a;Maven如何創建Java web項目&#xff08;純干貨版&#xff09;&#xff01;&#xff01;&#xff01;_明天更新的博客-CSDN博客 我們主要演示如何連接數據庫驗證登錄。 1.在webapp目錄下創建我們的登錄頁面&#xff1a;index.jsp 還需要再…

Android漏洞之戰——整體加殼原理和脫殼技巧詳解

一、前言 為了幫助更加方便的進行漏洞挖掘工作&#xff0c;前面我們通過了幾篇文章詳解的給大家介紹了動態調試技術、過反調試技術、Hook技術、過反Hook技術、抓包技術等&#xff0c;掌握了這些可以很方便的開展App漏洞挖掘工作&#xff0c;而最后我們還需要掌握一定的脫殼技巧…

opencv基礎:幾個常用窗口方法

開始說了一些opencv中的一些常用方法。 namedWindow方法 在OpenCV中&#xff0c;namedWindow函數用于創建一個窗口&#xff0c;并給它指定一個名字。這個函數的基本語法如下&#xff1a; import cv2cv2.namedWindow(窗口名稱, 標識 )窗口名稱&#xff1a;其實窗口名稱&…