數據結構丨前綴樹


前綴樹簡介

什么是前綴樹?

前綴樹N叉樹的一種特殊形式。通常來說,一個前綴樹是用來存儲字符串的。前綴樹的每一個節點代表一個字符串前綴)。每一個節點會有多個子節點,通往不同子節點的路徑上有着不同的字符。子節點代表的字符串是由節點本身的原始字符串,以及通往該子節點路徑上所有的字符組成的。

下面是前綴樹的一個例子:

img

在上圖示例中,我們在節點中標記的值是該節點對應表示的字符串。例如,我們從根節點開始,選擇第二條路徑 'b',然后選擇它的第一個子節點 'a',接下來繼續選擇子節點 'd',我們最終會到達葉節點 "bad"。節點的值是由從根節點開始,與其經過的路徑中的字符按順序形成的。

值得注意的是,根節點表示空字符串

前綴樹的一個重要的特性是,節點所有的后代都與該節點相關的字符串有着共同的前綴。這就是前綴樹名稱的由來。

我們再來看這個例子。例如,以節點 "b" 為根的子樹中的節點表示的字符串,都具有共同的前綴 "b"。反之亦然,具有公共前綴 "b" 的字符串,全部位於以 "b" 為根的子樹中,並且具有不同前綴的字符串來自不同的分支。

前綴樹有着廣泛的應用,例如自動補全,拼寫檢查等等。我們將在后面的章節中介紹實際應用場景。

如何表示一個前綴樹?

在前面的文章中,我們介紹了前綴樹的概念。在這篇文章中,我們將討論如何用代碼表示這個數據結構。

在閱讀一下內容前,請簡要回顧N叉樹的節點結構。

前綴樹的特別之處在於字符和子節點之間的對應關系。有許多不同的表示前綴樹節點的方法,這里我們只介紹其中的兩種方法。

方法一 - 數組

第一種方法是用數組存儲子節點。

例如,如果我們只存儲含有字母 az 的字符串,我們可以在每個節點中聲明一個大小為26的數組來存儲其子節點。對於特定字符 c,我們可以使用 c - 'a' 作為索引來查找數組中相應的子節點。

// change this value to adapt to different cases
#define N 26

struct TrieNode {
    TrieNode* children[N];
    
    // you might need some extra values according to different cases
};

/** Usage:
 *  Initialization: TrieNode root = new TrieNode();
 *  Return a specific child node with char c: (root->children)[c - 'a']
 */

訪問子節點十分快捷。訪問一個特定的子節點比較容易,因為在大多數情況下,我們很容易將一個字符轉換為索引。但並非所有的子節點都需要這樣的操作,所以這可能會導致空間的浪費

方法二 - Map

第二種方法是使用 Hashmap 來存儲子節點。

我們可以在每個節點中聲明一個Hashmap。Hashmap的鍵是字符,值是相對應的子節點。

struct TrieNode {
    unordered_map<char, TrieNode*> children;
    
    // you might need some extra values according to different cases
};

/** Usage:
 *  Initialization: TrieNode root = new TrieNode();
 *  Return a specific child node with char c: (root->children)[c]
 */

通過相應的字符來訪問特定的子節點更為容易。但它可能比使用數組稍慢一些。但是,由於我們只存儲我們需要的子節點,因此節省了空間。這個方法也更加靈活,因為我們不受到固定長度和固定范圍的限制。

補充

我們已經提到過如何表示前綴樹中的子節點。除此之外,我們也需要用到一些其他的值。

例如,我們知道,前綴樹的每個節點表示一個字符串,但並不是所有由前綴樹表示的字符串都是有意義的。如果我們只想在前綴樹中存儲單詞,那么我們可能需要在每個節點中聲明一個布爾值(Boolean)作為標志,來表明該節點所表示的字符串是否為一個單詞。

基本操作

Insertion in Trie

我們已經在另一張卡片中討論了 (如何在二叉搜索樹中實現插入操作)。

提問:

你還記得如何在二叉搜索樹中插入一個新的節點嗎?

當我們在二叉搜索樹中插入目標值時,在每個節點中,我們都需要根據 節點值目標值 之間的關系,來確定目標值需要去往哪個子節點。同樣地,當我們向前綴樹中插入一個目標值時,我們也需要根據插入的 目標值 來決定我們的路徑。

更具體地說,如果我們在前綴樹中插入一個字符串 S,我們要從根節點開始。 我們將根據 S[0](S中的第一個字符),選擇一個子節點或添加一個新的子節點。然后到達第二個節點,並根據 S[1] 做出選擇。 再到第三個節點,以此類推。 最后,我們依次遍歷 S 中的所有字符並到達末尾。 末端節點將是表示字符串 S 的節點。

下面是一個例子:

search

我們來用偽代碼總結一下以上策略:

1. Initialize: cur = root
2. for each char c in target string S:
3.      if cur does not have a child c:
4.          cur.children[c] = new Trie node
5.      cur = cur.children[c]
6. cur is the node which represents the string S

通常情況情況下,你需要自己構建前綴樹。構建前綴樹實際上就是多次調用插入函數。但請記住在插入字符串之前要 初始化根節點

Search in Trie

搜索前綴

正如我們在前綴樹的簡介中提到的,所有節點的后代都與該節點相對應字符串的有着共同前綴。因此,很容易搜索以特定前綴開頭的任何單詞。

同樣地,我們可以根據給定的前綴沿着樹形結構搜索下去。一旦我們找不到我們想要的子節點,搜索就以失敗終止。否則,搜索成功。為了更具體地解釋搜索的過程,我們提供了下列示例:

search2

我們來用偽代碼總結一下以上策略:

1. Initialize: cur = root
2. for each char c in target string S:
3.      if cur does not have a child c:
4.          search fails
5.      cur = cur.children[c]
6. search successes

搜索單詞

你可能還想知道如何搜索特定的單詞,而不是前綴。我們可以將這個詞作為前綴,並同樣按照上述同樣的方法進行搜索。

  1. 如果搜索失敗,那么意味着沒有單詞以目標單詞開頭,那么目標單詞絕對不會存在於前綴樹中。
  2. 如果搜索成功,我們需要檢查目標單詞是否是前綴樹中單詞的前綴,或者它本身就是一個單詞。為了進一步解決這個問題,你可能需要稍對節點的結構做出修改。

提示:往每個節點中加入布爾值可能會有效地幫助你解決這個問題。

實現Trie(前綴樹)

實現一個 Trie (前綴樹),包含 insert, search, 和 startsWith 這三個操作。

示例:

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");   
trie.search("app");     // 返回 true

說明:

  • 你可以假設所有的輸入都是由小寫字母 a-z 構成的。
  • 保證所有輸入均為非空字符串。

遞歸解法

#include <iostream>
#include <vector>
#include <map>

using namespace std;

/// Trie Recursive version
class Trie{

private:
    struct Node{
        map<char, int> next;
        bool end = false;
    };
    vector<Node> trie;

public:
    Trie(){
        trie.clear();
        trie.push_back(Node());
    }

    /** Inserts a word into the trie. */
    void insert(const string& word){
        insert(0, word, 0);
    }

    /** Returns if the word is in the trie. */
    bool search(const string& word){
        return search(0, word, 0);
    }

    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(const string& prefix) {
        return startsWith(0, prefix, 0);
    }

private:
    void insert(int treeID, const string& word, int index){

        if(index == word.size()) {
            trie[treeID].end = true;
            return;
        }

        if(trie[treeID].next.find(word[index]) == trie[treeID].next.end()){
            trie[treeID].next[word[index]] = trie.size();
            trie.push_back(Node());
        }

        insert(trie[treeID].next[word[index]], word, index + 1);
    }

    bool search(int treeID, const string& word, int index){

        if(index == word.size())
            return trie[treeID].end;

        if(trie[treeID].next.find(word[index]) == trie[treeID].next.end())
            return false;

        return search(trie[treeID].next[word[index]], word, index + 1);
    }

    bool startsWith(int treeID, const string& prefix, int index){

        if(index == prefix.size())
            return true;

        if(trie[treeID].next.find(prefix[index]) == trie[treeID].next.end())
            return false;

        return startsWith(trie[treeID].next[prefix[index]], prefix, index + 1);
    }
};


void printBool(bool res){
    cout << (res ? "True" : "False") << endl;
}

int main() {

    Trie trie1;
    trie1.insert("ab");
    printBool(trie1.search("a"));     // false
    printBool(trie1.startsWith("a")); // true;

    cout << endl;

    // ---

    Trie trie2;
    trie2.insert("a");
    printBool(trie2.search("a"));     // true
    printBool(trie2.startsWith("a")); // true;

    return 0;
}

非遞歸解法

#include <iostream>
#include <vector>
#include <map>

using namespace std;

/// Trie Recursive version
class Trie{

private:
    struct Node{
        map<char, int> next;
        bool end = false;
    };
    vector<Node> trie;
public: 
    Trie(){
        trie.clear();
        trie.push_back(Node());
    }

    void insert(const string& word){
        int treeID = 0;
        for(char c: word){
            //若未找到該節點
            if(trie[treeID].next.find(c) == trie[treeID].next.end()){
                trie[treeID].next[c] = trie.size();
                trie.push_back(Node());
            }
            treeID = trie[treeID].next[c];
        }
        trie[treeID].end = true;
    }
    bool search(const string& word){
        int treeID = 0;
        for(char c: word){
            if(trie[treeID].next.find(c)==trie[treeID].next.end())
                return false;
            treeID = trie[treeID].next[c];
        }
        return trie[treeID].end;
    }
    bool startsWith(const string& prefix){
        int treeID = 0;
        for(char c: prefix){
            if(trie[treeID].next.find(c)==trie[treeID].next.end())
                return false;
            treeID = trie[treeID].next[c];
        }
        return true;
    }
};

void printBool(bool res){
    cout << (res? "True" : "False") << endl;
}

int main() {

    Trie trie1;
    trie1.insert("ab");
    printBool(trie1.search("a"));     // false
    printBool(trie1.startsWith("a")); // true;

    cout << endl;

    // ---

    Trie trie2;
    trie2.insert("a");
    printBool(trie2.search("a"));     // true
    printBool(trie2.startsWith("a")); // true;

    return 0;
}

實際應用I

Map Sum Pairs

實現一個 MapSum 類里的兩個方法,insertsum

對於方法 insert,你將得到一對(字符串,整數)的鍵值對。字符串表示鍵,整數表示值。如果鍵已經存在,那么原來的鍵值對將被替代成新的鍵值對。

對於方法 sum,你將得到一個表示前綴的字符串,你需要返回所有以該前綴開頭的鍵的值的總和。

示例 1:

輸入: insert("apple", 3), 輸出: Null
輸入: sum("ap"), 輸出: 3
輸入: insert("app", 2), 輸出: Null
輸入: sum("ap"), 輸出: 5

參考https://www.cnblogs.com/grandyang/p/7616525.html

#include <iostream>
#include <bits/stdc++.h>

using namespace std;

//這道題讓我們實現一個MapSum類,里面有兩個方法,insert和sum,其中inser就是插入一個鍵值對,而sum方法比較特別,是在找一個前綴,需要將所有有此前綴的單詞的值累加起來返回。看到這種玩前綴的題,照理來說是要用前綴樹來做的。但是博主一般想偷懶,不想新寫一個結構或類,於是就使用map來代替前綴樹啦。博主開始想到的方法是建立前綴和一個pair之間的映射,這里的pair的第一個值表示該詞的值,第二個值表示將該詞作為前綴的所有詞的累加值,那么我們的sum函數就異常的簡單了,直接將pair中的兩個值相加即可。關鍵就是要在insert中把數據結構建好,構建的方法也不難,首先我們suppose原本這個key是有值的,我們更新的時候只需要加上它的差值即可,就算key不存在默認就是0,算差值也沒問題。然后我們將first值更新為val,然后就是遍歷其所有的前綴了,給每個前綴的second都加上diff即可,參見代碼如下:
class MapSum{
private: 
    unordered_map<string, pair<int, int>> m;
public:
    MapSum(){}

    void insert(string key, int val){
        //diff的作用防止重復插入
        int diff = val - m[key].first, n = key.size();
        m[key].first = val;
        for(int i=n-1; i>0; --i)
            m[key.substr(0, i)].second += diff;
    }
    int sum(string prefix){
        return m[prefix].first + m[prefix].second;
    }
};

//下面這種方法是論壇上投票最高的方法,感覺很叼,用的是帶排序的map,insert就是把單詞加入map。在map里會按照字母順序自動排序,然后在sum函數里,我們根據prefix來用二分查找快速定位到第一個不小於prefix的位置,然后向后遍歷,向后遍歷的都是以prefix為前綴的單詞,如果我們發現某個單詞不是以prefix為前綴了,直接break;否則就累加其val值,參見代碼如下:
class MapSum{
private:
    map<string, int> m;
public:
    MapSum(){}
    void insert(string key, int val){
        m[key] = val;
    }
    int sum(string prefix){
        int res = 0, n = prefix.size();
        for(auto it = m.lower_bound(prefix); it != m.end(); ++it){
            if(it->first.substr(0, n) != prefix) break;
            res += it->second;
        }
        return res;
    }
};

單詞替換

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

現在,給定一個由許多詞根組成的詞典和一個句子。你需要將句子中的所有繼承詞詞根替換掉。如果繼承詞有許多可以形成它的詞根,則用最短的詞根替換它。

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

示例 1:

輸入: dict(詞典) = ["cat", "bat", "rat"]
sentence(句子) = "the cattle was rattled by the battery"
輸出: "the cat was rat by the bat"

注:

  1. 輸入只包含小寫字母。
  2. 1 <= 字典單詞數 <=1000
  3. 1 <= 句中詞語數 <= 1000
  4. 1 <= 詞根長度 <= 100
  5. 1 <= 句中詞語長度 <= 1000

參考https://www.cnblogs.com/grandyang/p/7423420.html

#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

//這道題最好的解法其實是用前綴樹(Trie / Prefix Tree)來做,關於前綴樹使用之前有一道很好的入門題Implement Trie (Prefix Tree)。了解了前綴樹的原理機制,那么我們就可以發現這道題其實很適合前綴樹的特點。我們要做的就是把所有的前綴都放到前綴樹里面,而且在前綴的最后一個結點的地方將標示isWord設為true,表示從根節點到當前結點是一個前綴,然后我們在遍歷單詞中的每一個字母,我們都在前綴樹查找,如果當前字母對應的結點的表示isWord是true,我們就返回這個前綴,如果當前字母對應的結點在前綴樹中不存在,我們就返回原單詞,這樣就能完美的解決問題了。所以啊,以后遇到了有關前綴或者類似的問題,一定不要忘了前綴樹這個神器喲~
class Solution{
public: 
    class TrieNode{
        public: 
        bool isWord;
        TrieNode *child[26];
        // TrieNode(){};
        TrieNode(){
            isWord = false;
            for(auto &a : child) a = NULL;
        };
    };
    string replaceWords(vector<string>& dict, string sentence){
        string res = "", t="";
        istringstream is(sentence);
        TrieNode* root = new TrieNode();
        for(string word: dict){
            insert(root, word);
        }
        while(is >> t){
            if(!res.empty()) res += " ";
            res += findPrefix(root, t);
        }
        return res;
    }
    void insert(TrieNode* node, string word){
        for(char c: word){
            if(!node->child[c-'a']) node->child[c-'a'] = new TrieNode();
            node = node->child[c-'a'];
        }
        node->isWord = true;
    }
    string findPrefix(TrieNode* node, string word){
        string cur = "";
        for(char c: word){
            if(!node->child[c-'a']) break;
            cur.push_back(c);
            node = node->child[c - 'a'];
            if(node->isWord) return cur;
        }
        return word;
    }
};

添加與搜索單詞 - 數據結構設計

設計一個支持以下兩種操作的數據結構:

void addWord(word)
bool search(word)

search(word) 可以搜索文字或正則表達式字符串,字符串只包含字母 .a-z. 可以表示任何一個字母。

示例:

addWord("bad")
addWord("dad")
addWord("mad")
search("pad") -> false
search("bad") -> true
search(".ad") -> true
search("b..") -> true

說明:

你可以假設所有單詞都是由小寫字母 a-z 組成的。

#include <iostream>
#include <vector>
using namespace std;

class WordDictionary{
private:
    struct TrieNode{
        bool isWord;
        vector<TrieNode*> children;
        TrieNode(): isWord(false), children(26, nullptr){}
        ~TrieNode(){
            for(TrieNode* child: children)
                if(child) delete child;
        }
    };
    TrieNode* trieRoot;
    bool myFind(string &str, TrieNode* nowPtr, int nowIndex){
        int strSize = str.size();
        if(nowPtr == NULL){
            return false;
        }
        if(nowIndex >= strSize){
            if(nowPtr->isWord){
                return true;
            }
            return false;
        }
        else if(str[nowIndex] != '.'){
            if(nowPtr->children[str[nowIndex] - 'a'] != NULL){
                return myFind(str, nowPtr->children[str[nowIndex] - 'a'], nowIndex+1);
            }
            return false;
        }
        else{
            for(int i=0; i<26; ++i){
                if(nowPtr->children[i] != NULL && myFind(str, nowPtr->children[i], nowIndex+1 )){
                    return true;
                }
            }
        }
        return false;
    }
public: 
    WordDictionary(){
        trieRoot = new TrieNode();
    }
    void addWord(string word){
        TrieNode * ptr = trieRoot;
        for(auto ch : word){
            if(ptr->children[ch - 'a'] == NULL){
                ptr->children[ch - 'a'] = new TrieNode();
            }
            ptr = ptr->children[ch - 'a'];
        }
        ptr->isWord = true;
    }
    bool search(string word){
        return myFind(word, trieRoot, 0);
    }
};

實際應用II

數組中兩個樹的最大異或值

給定一個非空數組,數組中元素為 a0, a1, a2, … , an-1,其中 0 ≤ ai < 2^31 。

找到 ai 和aj 最大的異或 (XOR) 運算結果,其中0 ≤ i, j < n

你能在O(n)的時間解決這個問題嗎?

示例:

輸入: [3, 10, 5, 25, 2, 8]

輸出: 28

解釋: 最大的結果是 5 ^ 25 = 28.
//https://blog.csdn.net/weijiechenlun133/article/details/70135937
class SolutionA
{
public:
    int findMaximumXOR(vector<int> &nums)
    {
        if (nums.size() < 2)    return 0;
        int maxNum = 0;
        int flag = 0;
        for(int i = 31; i>=0; --i){
            set<int> hash;

            flag |= (1 << i);
            for(int x:nums)
                hash.insert(flag & x);

            int tmp = maxNum | (1<<i);
            for(int x:hash){
                if(hash.find(x^tmp)!=hash.end()){
                    maxNum = tmp;
                    break;
                }
            }
        }
        return maxNum;
    }
};

struct Node{
    Node* next[2];
    Node(){
        next[0] = nullptr;
        next[1] = nullptr;
    }
};
class SolutionB{
public:
    void buildTrieTree(Node* root, int x){
        for(int i = 31; i>=0; --i){
            int flag = (x & (1<<i) )? 1:0;
            if(root->next[flag] == nullptr){
                root->next[flag] = new Node();
            }
            root = root->next[flag];
        }
    }
    int findMaxXorInTire(Node* root, int x){
        int result = 0;
        for(int i = 31; i>=0; --i){
            int flag = (x & (1<<i) )? 0:1;
            if(root->next[flag] != nullptr){
                result |= (1<<i);   //result = result | (1<<i)
                root = root->next[flag];
            }
            else
                root = root->next[1-flag];
        }
        return result;
    }
    int findMaximumXOR(vector<int>& nums){
        if(nums.size()<2) return 0;
        Node head;
        for(int x : nums)
            buildTrieTree(&head, x);
        int maxNum = 0;
        for(int x: nums){
            int m = findMaxXorInTire(&head, x);
            maxNum = max(maxNum, m);
        }
        return maxNum;
    }
};

單詞搜索II

給定一個二維網格 board 和一個字典中的單詞列表 words,找出所有同時在二維網格和字典中出現的單詞。

單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母在一個單詞中不允許被重復使用。

示例:

輸入: 
words = ["oath","pea","eat","rain"] and board =
[
  ['o','a','a','n'],
  ['e','t','a','e'],
  ['i','h','k','r'],
  ['i','f','l','v']
]

輸出: ["eat","oath"]

說明:
你可以假設所有輸入都由小寫字母 a-z 組成。

提示:

  • 你需要優化回溯算法以通過更大數據量的測試。你能否早點停止回溯?
  • 如果當前單詞不存在於所有單詞的前綴中,則可以立即停止回溯。什么樣的數據結構可以有效地執行這樣的操作?散列表是否可行?為什么? 前綴樹如何?如果你想學習如何實現一個基本的前綴樹,請先查看這個問題: 實現Trie(前綴樹)

參考:https://blog.csdn.net/qq_41855420/article/details/88064909

#include <iostream>
#include <vector>

using namespace std;

//前綴樹的程序表示
class TrieNode {
public:
	bool isWord;//當前節點為結尾是否是字符串
	vector<TrieNode*> children;
	TrieNode() : isWord(false), children(26, nullptr) {}
	~TrieNode() {
		for (TrieNode* child : children)
			if (child) delete child;
	}
};
class Solution {
private:
	TrieNode * trieRoot;//構建的單詞前綴樹
	//在樹中插入一個單詞的方法實現
	void addWord(string word) {
		TrieNode *ptr = trieRoot;//掃描這棵樹,將word插入
		//將word的字符逐個插入
		for (auto ch : word) {
			if (ptr->children[ch - 'a'] == NULL) {
				ptr->children[ch - 'a'] = new TrieNode();
			}
			ptr = ptr->children[ch - 'a'];
		}
		ptr->isWord = true;//標記為單詞
	}
public:
	int rowSize;//board的行數
	int colSize;//board的列數
	vector<vector<bool>> boardFlag;//標記board[row][col]是否已使用
	//以board[row][col]為中心點,四個方向進行嘗試搜索
	void dfs(vector<vector<char>>& board, vector<string> &result, string &tempRes, TrieNode * nowRoot, int row, int col) {
		if (nowRoot == NULL) {
			return;
		}
		if (nowRoot->isWord) {//如果這個單詞成功找到
			result.push_back(tempRes);//放入結果
			nowRoot->isWord = false;//將這個單詞標記為公共后綴 防止重復
		}
		string tempResAdd;
		//上方測試
		//如果上方未出界,沒有被使用,且nowRoot->children中存在相等的節點
		if (row - 1 >= 0 && !boardFlag[row - 1][col] && nowRoot->children[board[row - 1][col] - 'a'] != NULL) {
			boardFlag[row - 1][col] = true;//標記使用
			tempResAdd = tempRes + char(board[row - 1][col]);
			dfs(board, result, tempResAdd, nowRoot->children[board[row - 1][col] - 'a'], row - 1, col);
			boardFlag[row - 1][col] = false;//取消標記
		}
		//下方測試
		//如果下方未出界,沒有被使用,且nowRoot->children中存在相等的節點
		if (row + 1 < rowSize && !boardFlag[row + 1][col] && nowRoot->children[board[row + 1][col] - 'a'] != NULL) {
			boardFlag[row + 1][col] = true;//標記使用
			tempResAdd = tempRes + char(board[row + 1][col]);
			dfs(board, result, tempResAdd, nowRoot->children[board[row + 1][col] - 'a'], row + 1, col);
			boardFlag[row + 1][col] = false;//取消標記
		}
		//左方測試
		//如果左方未出界,沒有被使用,且nowRoot->children中存在相等的節點
		if (col - 1 >= 0 && !boardFlag[row][col - 1] && nowRoot->children[board[row][col - 1] - 'a'] != NULL) {
			boardFlag[row][col - 1] = true;//標記使用
			tempResAdd = tempRes + char(board[row][col - 1]);
			dfs(board, result, tempResAdd, nowRoot->children[board[row][col - 1] - 'a'], row, col - 1);
			boardFlag[row][col - 1] = false;//取消標記
		}
		//右方測試
		//如果右方未出界,沒有被使用,且nowRoot->children中存在相等的節點
		if (col + 1 < colSize && !boardFlag[row][col + 1] && nowRoot->children[board[row][col + 1] - 'a'] != NULL) {
			boardFlag[row][col + 1] = true;//標記使用
			tempResAdd = tempRes + char(board[row][col + 1]);
			dfs(board, result, tempResAdd, nowRoot->children[board[row][col + 1] - 'a'], row, col + 1);
			boardFlag[row][col + 1] = false;//取消標記
		}
	}
	vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
		rowSize = board.size();
		if (rowSize == 0) {
			return {};
		}
		colSize = board[0].size();
		boardFlag = vector<vector<bool>>(rowSize, vector<bool>(colSize, false));//構建標記容器
		trieRoot = new TrieNode();//單詞后綴樹
        //將單詞都放入前綴樹中
		for (auto word : words) {
			addWord(word);
		}
		vector<string> result;//用於存儲結果
		string tempRes;
		for (int row = 0; row < rowSize; ++row) {
			for (int col = 0; col < colSize; ++col) {
				if (trieRoot->children[board[row][col] - 'a'] != NULL) {//搜索
					tempRes = "";
					tempRes += char(board[row][col]);
					boardFlag[row][col] = true;//標記使用
					dfs(board, result, tempRes, trieRoot->children[board[row][col] - 'a'], row, col);
					boardFlag[row][col] = false;//取消使用
				}
			}
		}
		return result;
	}
};

回文對

給定一組唯一的單詞, 找出所有不同 的索引對(i, j),使得列表中的兩個單詞, words[i] + words[j] ,可拼接成回文串。

示例 1:

輸入: ["abcd","dcba","lls","s","sssll"]
輸出: [[0,1],[1,0],[3,2],[2,4]] 
解釋: 可拼接成的回文串為 ["dcbaabcd","abcddcba","slls","llssssll"]

示例 2:

輸入: ["bat","tab","cat"]
輸出: [[0,1],[1,0]] 
解釋: 可拼接成的回文串為 ["battab","tabbat"]

大多數解法都是基於hash表,看着很復雜,我找到一個可讀性比較高的版本,之后還得拿出來溫習。

#include <iostream>
#include <vector>
#include <bits/stdc++.h>
#include <string>

using namespace std;

class Solution{
public: 
    bool isPalindrome(string& s, int start, int end){
        while(start < end)
            if(s[start++] != s[end--])
                return false;
            return true;
    }
    vector<vector<int>> palindromePairs(vector<string> words){
        vector<vector<int>> ans;
        unordered_map<string, int> dict;
        int len = words.size();
        for(int i=0; i<len; i++)
            dict[words[i]] = i;
        for(int i=0; i<len; i++){
            string cur = words[i];
            int clen = cur.size();
            for(int j=0; j<=clen; j++){
                //找后綴
                if(isPalindrome(cur, j, clen - 1)){
                    string suffix = cur.substr(0, j);
                    reverse(suffix.begin(), suffix.end());
                    if(dict.find(suffix)!=dict.end() && i!=dict[suffix])
                        ans.push_back({i, dict[suffix]});
                }
                //找前綴
                if(j>0 && isPalindrome(cur, 0, j-1)){
                    string prefix = cur.substr(j);
                    reverse(prefix.begin(), prefix.end());
                    if(dict.find(prefix) != dict.end() && i!=dict[prefix])
                        ans.push_back({dict[prefix], i});
                }
            }
        }
        return ans;
    }
};

int main(){
    vector<string> a = {"lls", "s", "sssll"};
    Solution s = Solution();
    vector<vector<int>> v =  s.palindromePairs(a);
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM