745. Prefix and Suffix Search

Description

Given many words, words[i] has weight i.

Design a class WordFilter that supports one function, WordFilter.f(String prefix, String suffix). It will return the word with given prefix and suffix with maximum weight. If no word exists, return -1.

Examples:

Input:
WordFilter(["apple"])
WordFilter.f("a", "e") // returns 0
WordFilter.f("b", "") // returns -1

Note:

  1. words has length in range [1, 15000].
  2. For each test case, up to words.length queries WordFilter.f may be made.
  3. words[i] has length in range [1, 10].
  4. prefix, suffix have lengths in range [0, 10].
  5. words[i] and prefix, suffix queries consist of lowercase letters only.

Solution

Prefix tree and Suffix tree, time O(nk) + O(q * (n + k)), space O(nk)

n: words length
k: max word length
q: f query count

class WordFilter {
    private Trie prefixTrie;
    private Trie suffixTrie;

    public WordFilter(String[] words) {
        prefixTrie = new Trie();
        suffixTrie = new Trie();
        
        for (int i = 0; i < words.length; ++i) {
            String word = words[i];
            // build prefix trie
            Trie curr = prefixTrie;
            for (int j = 0; j < word.length(); ++j) {
                int k = word.charAt(j) - 'a';
                if (curr.children[k] == null) {
                    curr.children[k] = new Trie();
                }
                curr = curr.children[k];
            }
            
            curr.isWord = true;
            curr.weight = i;
            // build suffix trie
            curr = suffixTrie;
            for (int j = word.length() - 1; j >= 0; --j) {
                int k = word.charAt(j) - 'a';
                if (curr.children[k] == null) {
                    curr.children[k] = new Trie();
                }
                curr = curr.children[k];
            }
            
            curr.isWord = true;
            curr.weight = i;
        }
    }
    
    public int f(String prefix, String suffix) {
        PriorityQueue<Integer> prefixQueue = new PriorityQueue<>((a, b) -> (b - a));
        PriorityQueue<Integer> suffixQueue = new PriorityQueue<>((a, b) -> (b - a));
        getWords(prefixTrie, prefix, prefixQueue);
        getWords(suffixTrie, new StringBuilder(suffix).reverse().toString(), suffixQueue);
        // get the largest common element of two priorityQueue
        while (!prefixQueue.isEmpty() && !suffixQueue.isEmpty()) {
            if (prefixQueue.peek() > suffixQueue.peek()) {
                prefixQueue.poll();
            } else if (prefixQueue.peek() < suffixQueue.peek()) {
                suffixQueue.poll();
            } else {
                return prefixQueue.poll();
            }
        }
        
        return -1;
    }
    
    public void getWords(Trie root, String s, PriorityQueue<Integer> queue) {
        for (int i = 0; i < s.length() && root != null; ++i) {
            root = root.children[s.charAt(i) - 'a'];
        }
        
        if (root == null) {
            return;
        }
        
        getWords(root, queue);
    }
    
    public void getWords(Trie root, PriorityQueue<Integer> queue) {
        if (root.isWord) {  // add root first!
            queue.offer(root.weight);
        }
        
        for (int i = 0; i < root.children.length; ++i) {
            if (root.children[i] == null) {
                continue;
            }
            
            getWords(root.children[i], queue);
        }
    }
    
    class Trie {
        Trie[] children;
        int weight;
        boolean isWord;
        
        public Trie() {
            children = new Trie[26];
        }
    }
}

/**
 * Your WordFilter object will be instantiated and called as such:
 * WordFilter obj = new WordFilter(words);
 * int param_1 = obj.f(prefix,suffix);
 */

Two Tries, store information in parent node, time O(nk) + O(q * (n + k)), space O(nk)

由于f函数会被多次调用,每次都找到某个prefix下的所有节点太耗时了,我们可以将子节点的信息存储在从根到它路径上的所有父节点中,这样只需要找到prefix到达的那个节点,就知道它的子节点的weights了。

下面这种写法竟然TLE也是醉醉的,理论上应该比第一种解法快很多啊...WordFilter的初始化会慢一些(因为增加了set操作),但是f方法会快,不过看样子也快不了多少,因为总的words count就不太多。

class WordFilter {
    private Trie prefixTrie;
    private Trie suffixTrie;

    public WordFilter(String[] words) {
        prefixTrie = new Trie();
        suffixTrie = new Trie();
        
        for (int i = 0; i < words.length; ++i) {
            String word = words[i];
            // build prefix trie
            Trie curr = prefixTrie;
            curr.weights.add(i);
            for (int j = 0; j < word.length(); ++j) {
                int k = word.charAt(j) - 'a';
                if (curr.children[k] == null) {
                    curr.children[k] = new Trie();
                }
                curr = curr.children[k];
                curr.weights.add(i);
            }
            
            // build suffix trie
            curr = suffixTrie;
            curr.weights.add(i);
            for (int j = word.length() - 1; j >= 0; --j) {
                int k = word.charAt(j) - 'a';
                if (curr.children[k] == null) {
                    curr.children[k] = new Trie();
                }
                curr = curr.children[k];
                curr.weights.add(i);
            }
        }
    }
    
    public int f(String prefix, String suffix) {
        Set<Integer> prefixWeights = findWeights(prefixTrie, prefix);
        Set<Integer> suffixWeights = findWeights(suffixTrie, new StringBuilder(suffix).reverse().toString());
        int maxWeight = -1;
        
        for (int weight : prefixWeights) {
            if (suffixWeights.contains(weight) && maxWeight < weight) {
                maxWeight = weight;
            }
        }
        
        return maxWeight;
    }

    public Set<Integer> findWeights(Trie root, String s) {
        for (int i = 0; i < s.length() && root != null; ++i) {
            root = root.children[s.charAt(i) - 'a'];
        }
        
        return root == null ? Collections.EMPTY_SET : root.weights;
    }
    
    class Trie {
        Trie[] children;
        Set<Integer> weights;
        
        public Trie() {
            children = new Trie[26];
            weights = new HashSet<>();
        }
    }
}

/**
 * Your WordFilter object will be instantiated and called as such:
 * WordFilter obj = new WordFilter(words);
 * int param_1 = obj.f(prefix,suffix);
 */

Trie of Suffix Wrapped Words

Consider the word 'apple'. For each suffix of the word, we could insert that suffix, followed by '#', followed by the word, all into the trie.

For example, we will insert '#apple', 'e#apple', 'le#apple', 'ple#apple', 'pple#apple', 'apple#apple' into the trie. Then for a query like prefix = "ap", suffix = "le", we can find it by querying our trie for le#ap.

Complexity Analysis

Time Complexity: O(NK^2 + QK) where N is the number of words, K is the maximum length of a word, and Q is the number of queries.

Space Complexity: O(NK^2), the size of the trie.

class WordFilter {
    TrieNode trie;
    public WordFilter(String[] words) {
        trie = new TrieNode();
        for (int weight = 0; weight < words.length; ++weight) {
            String word = words[weight] + "{";
            for (int i = 0; i < word.length(); ++i) {
                TrieNode cur = trie;
                cur.weight = weight;
                for (int j = i; j < 2 * word.length() - 1; ++j) {
                    int k = word.charAt(j % word.length()) - 'a';
                    if (cur.children[k] == null)
                        cur.children[k] = new TrieNode();
                    cur = cur.children[k];
                    cur.weight = weight;
                }
            }
        }
    }
    public int f(String prefix, String suffix) {
        TrieNode cur = trie;
        for (char letter: (suffix + '{' + prefix).toCharArray()) {
            if (cur.children[letter - 'a'] == null) return -1;
            cur = cur.children[letter - 'a'];
        }
        return cur.weight;
    }
}

class TrieNode {
    TrieNode[] children;
    int weight;
    public TrieNode() {
        children = new TrieNode[27];
        weight = 0;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,322评论 0 10
  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,475评论 0 23
  • 细读这本书,还不到一半,发现这本书的写作风格类似科学论文,非常严谨,详细!先提问,举例,然后佐证,再结论,作者...
    羊毛风铃12阅读 142评论 0 0
  • 图/文 : 茉莉 那时候 竹子做的剑,划过小脸 猫咪最爱将你舔 那一天 爷爷突然反对咱见面 镜子被我摔成碎片 那一...
    茉莉的小茶馆阅读 283评论 13 7
  • 哈喽,大家好,今天的笑笑再次和大家见面了。 今天训练营的演讲课题是梦想。 你有梦想吗?你的梦想是什么?如果我问一个...
    笑莉说阅读 225评论 10 13