648. 單詞替換 : 字典樹的經(jīng)典運用

題目描述

這是 LeetCode 上的 648. 單詞替換 ,難度為 中等

Tag : 「字典樹」

在英語中,我們有一個叫做 詞根(root) 的概念,可以詞根后面添加其他一些詞組成另一個較長的單詞——我們稱這個詞為 繼承詞(successor)檩禾。例如,詞根an,跟隨著單詞 other(其他)略水,可以形成新的單詞 another(另一個)。

現(xiàn)在赤赊,給定一個由許多詞根組成的詞典 dictionary 和一個用空格分隔單詞形成的句子 sentence闯狱。你需要將句子中的所有繼承詞用詞根替換掉。如果繼承詞有許多可以形成它的詞根抛计,則用最短的詞根替換它哄孤。

你需要輸出替換之后的句子。

示例 1:

輸入:dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery"

輸出:"the cat was rat by the bat"

示例 2:

輸入:dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs"

輸出:"a a b c"

提示:

  • 1 <= dictionary.length <= 1000
  • 1 <= dictionary[i].length <= 100
  • dictionary[i] 僅由小寫字母組成吹截。
  • 1 <= sentence.length <= 10^6
  • sentence 僅由小寫字母和空格組成瘦陈。
  • sentence 中單詞的總量在范圍 [1, 1000] 內。
  • sentence 中每個單詞的長度在范圍 [1, 1000] 內波俄。
  • sentence 中單詞之間由一個空格隔開晨逝。
  • sentence 沒有前導或尾隨空格。

基本分析

這是一道 Trie 的模板題懦铺,還不了解 Trie 的同學可以先看前置 ??:【設計數(shù)據(jù)結構】實現(xiàn) Trie (前綴樹)

前置 ?? 通過圖解形式講解了 Trie 的結構與原理捉貌,以及提供了兩種實現(xiàn) Trie 的方式。

回到本題冬念,為了方便趁窃,我們令 dsdictionary,令 ssentence急前。

二維數(shù)組

一個比較習慣的做法醒陆,是使用「二維數(shù)組」來實現(xiàn) Trie,配合 static 優(yōu)化裆针,可以有效控制 new 的次數(shù)刨摩,耗時相對穩(wěn)定。

考慮兩個 Trie 的基本操作:

  • add 操作:變量入?yún)⒆址?s据块,將字符串中的每位字符映射到 [0, 25]码邻,同時為了能夠方便查詢某個字符串(而不只是某個前綴)是否曾經(jīng)存入過 Trie 中,額外使用一個布爾數(shù)組 isEnd 記錄某個位置是否為單詞結尾另假。
  • query 操作:

至于二維數(shù)組的大小估算像屋,可以直接開成 N \times C,其中 N 為要插入到 Trie 中的字符總數(shù)边篮,C 為對應的字符集大小己莺。在 N \times C 沒有 MLE 風險時奏甫,可以直接開這么多;而當 N \times C 較大(超過 1e7凌受,甚至 1e8 時)阵子,可以適當將 N \times C 中的 N 減少,使得總空間在 1e7 左右胜蛉,因為實際上由于二維數(shù)組中的某些行中會存儲一個字符以上挠进,實際上我們用不到這么多行。

代碼(不使用 static 優(yōu)化誊册,耗時增加十倍):

class Solution {
    static int N = 100000, M = 26;
    static int[][] tr = new int[N][M];
    static boolean[] isEnd = new boolean[N * M];
    static int idx;
    void add(String s) {
        int p = 0;
        for (int i = 0; i < s.length(); i++) {
            int u = s.charAt(i) - 'a';
            if (tr[p][u] == 0) tr[p][u] = ++idx;
            p = tr[p][u];
        }
        isEnd[p] = true;
    }
    String query(String s) {
        for (int i = 0, p = 0; i < s.length(); i++) {
            int u = s.charAt(i) - 'a';
            if (tr[p][u] == 0) break;
            if (isEnd[tr[p][u]]) return s.substring(0, i + 1);
            p = tr[p][u];
        }
        return s;
    }
    public String replaceWords(List<String> ds, String s) {
        for (int i = 0; i <= idx; i++) {
            Arrays.fill(tr[i], 0);
            isEnd[i] = false;
        }
        for (String d : ds) add(d);
        StringBuilder sb = new StringBuilder();
        for (String str : s.split(" ")) sb.append(query(str)).append(" ");
        return sb.substring(0, sb.length() - 1);
    }
}
  • 時間復雜度:令 n = \sum_{i = 0}^{ds.length - 1} ds[i].length领突,ms 長度,復雜度為 O(n + m)
  • 空間復雜度:O(n \times C)案怯,其中 C = 26 為字符集大小

TrieNode

另外一個能夠有效避免「估數(shù)組大小」操作的方式君旦,是使用 TrieNode 的方式實現(xiàn) Trie:每次使用到新的格子再觸發(fā) new 操作。

至于為什么有了 TrieNode 的方式嘲碱,我還是會采用「二維數(shù)組」優(yōu)先的做法金砍,在 知乎 上有同學問過我類似的問題,只不過原問題是「為什么有了動態(tài)開點線段樹麦锯,直接 build4n 空間的做法仍有意義」恕稠,這對應到本題使用「二維數(shù)組」還是「TrieNode」是一樣的道理:

除非某些語言在啟動時,采用虛擬機的方式扶欣,并且預先分配了足夠的內存谱俭,否則所有的 new 操作都需要反映到 os 上,而在 linux 分配時需要遍歷紅黑樹宵蛀,因此即使是總空間一樣昆著,一次性的 new 要比多次小空間的 new 更省時間,同時集中性的 new 也比分散性的 new 操作要更快术陶,這也就是為什么我們不無腦使用 TrieNode 的原因凑懂。

代碼:

class Solution {
    class Node {
        boolean isEnd;
        Node[] tns = new Node[26];
    }
    Node root = new Node();
    void add(String s) {
        Node p = root;
        for (int i = 0; i < s.length(); i++) {
            int u = s.charAt(i) - 'a';
            if (p.tns[u] == null) p.tns[u] = new Node();
            p = p.tns[u];
        }
        p.isEnd = true;
    }
    String query(String s) {
        Node p = root;
        for (int i = 0; i < s.length(); i++) {
            int u = s.charAt(i) - 'a';
            if (p.tns[u] == null) break;
            if (p.tns[u].isEnd) return s.substring(0, i + 1);
            p = p.tns[u];
        }
        return s;
    }
    public String replaceWords(List<String> ds, String s) {
        for (String str : ds) add(str);
        StringBuilder sb = new StringBuilder();
        for (String str : s.split(" ")) sb.append(query(str)).append(" ");
        return sb.substring(0, sb.length() - 1);
    }
}
  • 時間復雜度:令 n = \sum_{i = 0}^{ds.length - 1} ds[i].lengthms 長度梧宫,復雜度為 O(n + m)
  • 空間復雜度:O(n \times C)接谨,其中 C = 26 為字符集大小

加餐

今天額外增加一道更貼合筆試面試的加餐題 : 結合 DFS 的 Trie 運用題 ????

最后

這是我們「刷穿 LeetCode」系列文章的第 No.648 篇,系列開始于 2021/01/01塘匣,截止于起始日 LeetCode 上共有 1916 道題目脓豪,部分是有鎖題,我們將先把所有不帶鎖的題目刷完忌卤。

在這個系列文章里面扫夜,除了講解解題思路以外,還會盡可能給出最為簡潔的代碼。如果涉及通解還會相應的代碼模板笤闯。

為了方便各位同學能夠電腦上進行調試和提交代碼堕阔,我建立了相關的倉庫:https://github.com/SharingSource/LogicStack-LeetCode

在倉庫地址里颗味,你可以看到系列文章的題解鏈接超陆、系列文章的相應代碼、LeetCode 原題鏈接和其他優(yōu)選題解浦马。

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

本文由mdnice多平臺發(fā)布

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末时呀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子晶默,更是在濱河造成了極大的恐慌退唠,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荤胁,死亡現(xiàn)場離奇詭異,居然都是意外死亡屎债,警方通過查閱死者的電腦和手機仅政,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盆驹,“玉大人圆丹,你說我怎么就攤上這事∏” “怎么了辫封?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長廉丽。 經(jīng)常有香客問我倦微,道長,這世上最難降的妖魔是什么正压? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任欣福,我火速辦了婚禮,結果婚禮上焦履,老公的妹妹穿的比我還像新娘拓劝。我一直安慰自己,他們只是感情好嘉裤,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布郑临。 她就那樣靜靜地躺著,像睡著了一般屑宠。 火紅的嫁衣襯著肌膚如雪厢洞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音犀变,去河邊找鬼妹孙。 笑死,一個胖子當著我的面吹牛获枝,可吹牛的內容都是我干的蠢正。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼省店,長吁一口氣:“原來是場噩夢啊……” “哼嚣崭!你這毒婦竟也來了?” 一聲冷哼從身側響起懦傍,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雹舀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后粗俱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體说榆,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年寸认,在試婚紗的時候發(fā)現(xiàn)自己被綠了签财。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡偏塞,死狀恐怖唱蒸,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情灸叼,我是刑警寧澤神汹,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站古今,受9級特大地震影響屁魏,放射性物質發(fā)生泄漏。R本人自食惡果不足惜捉腥,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一蚁堤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧但狭,春花似錦披诗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至唱歧,卻和暖如春宪摧,著一層夾襖步出監(jiān)牢的瞬間粒竖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工几于, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蕊苗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓沿彭,卻偏偏與公主長得像朽砰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子喉刘,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內容