前缀树

前缀树主要用来解决与字符串查找相关的问题。如果字符串的长度为k,由于在前缀树中查找一个字符串相当于顺着前缀树的路径查找字符串的每个字符,因此时间复杂度是O(k)。

在哈希表中,只有输入完整的字符串才能进行查找操作,在前缀树中就没有这个限制。例如,可以只输入字符串的前面若干字符,即前缀,查找以这个前缀开头的所有字符串。如果要求根据字符串的前缀进行查找,那么合理应用前缀树可能是解决这个问题的关键。

  1. 实现前缀树

/**
 * Initialize your data structure here.
 */
var Trie = function() {
    this.children = {}; 
};

/**
 * Inserts a word into the trie. 
 * @param {string} word
 * @return {void}
 */
Trie.prototype.insert = function(word) {
    let node = this.children;
    for (const ch of word) {
        if (!node[ch]) {
            node[ch] = {};
        }
        node = node[ch];
    }
    node.isEnd = true;
};

Trie.prototype.searchPrefix = function(prefix) {
     let node = this.children;
     for (const ch of prefix) {
        if(!node[ch]) {
             return false
        }
        node = node[ch]
     }
      return node;
}

/**
 * Returns if the word is in the trie. 
 * @param {string} word
 * @return {boolean}
 */
Trie.prototype.search = function(word) {
    const node = this.searchPrefix(word)
    return node !== undefined && node.isEnd !== undefined;
};

/**
 * Returns if there is any word in the trie that starts with the given prefix. 
 * @param {string} prefix
 * @return {boolean}
 */
Trie.prototype.startsWith = function(prefix) {
    return this.searchPrefix(prefix);
};
  1. 替换单词
英语中有一个概念叫词根。在词根后面加上若干字符就能拼出更长的单词。例如,"an"是一个词根,在它后面加上"other"就能得到另一个单词"another"。现在给定一个由词根组成的字典和一个英语句子,如果句子中的单词在字典中有它的词根,则用它的词根替换该单词;如果单词没有词根,则保留该单词。请输出替换后的句子。例如,如果词根字典包含字符串["cat","bat","rat"],英语句子为"the cattle was rattled by the battery",则替换之后的句子是"the cat was rat by the bat"。
/**
 * @param {string[]} dictionary
 * @param {string} sentence
 * @return {string}
 */
var replaceWords = function(dictionary, sentence) {
    let children = {}
    // 构建前缀树
    for(let word of dictionary) {
        let node = children;
        for(let ch of word) {
            if(!node[ch]) {
                node[ch] = {}
            }
            node = node[ch]
        }
        node.isEnd = true
    }

    return sentence.split(' ').map(word => {
        let node = children, tmp = ''
        for (let w of word) {
            node = node[w]
            tmp += w
            if (!node || node.isEnd) break          
        }
        return node && node.isEnd ? tmp : word
    }).join(' ')

};
  1. 神奇的字典
题目:请实现有如下两个操作的神奇字典。
● 函数buildDict,输入单词数组用来创建一个字典
● 函数search,输入一个单词,判断能否修改该单词中的一个字符,使修改之后的单词是字典中的一个单词。
/**
 * Initialize your data structure here.
 */
var MagicDictionary = function() {
    this.dictionary = []
};

/** 
 * @param {string[]} dictionary
 * @return {void}
 */
MagicDictionary.prototype.buildDict = function(dictionary) {
    this.dictionary = dictionary
};

/** 
 * @param {string} searchWord
 * @return {boolean}
 */
MagicDictionary.prototype.search = function(searchWord) {
    for (const dict of this.dictionary) {
        if (dict.length == searchWord.length) {
            let diff = 0
            for (let i = 0; i < dict.length; i++) {
                if (dict[i] != searchWord[i]) diff++
                if (diff > 1) break
            }
            if (diff == 1) return true
        }
    }
    return false
};
  1. 最短的单词编码
单词数组 words 的 有效编码 由任意助记字符串 s 和下标数组 indices 组成,且满足:
words.length == indices.length
助记字符串 s 以 '#' 字符结尾
对于每个下标 indices[i] ,s 的一个从 indices[i] 开始、到下一个 '#' 字符结束(但不包括 '#')的 子字符串 恰好与 words[i] 相等

/**
 * @param {string[]} words
 * @return {number}
 */
var minimumLengthEncoding = function(words) {
    const set = new Set(words)
    for (const word of set) {
        for (let i = 1; i < word.length; i++) set.delete(word.substring(i))
    }
    let sum = 0
    for (const word of set) {
        sum += word.length + 1
    }
    return sum
};
  1. 单词之和
/**
 * Initialize your data structure here.
 */
var MapSum = function() {
    this.map = new Map();
    this.prefixmap = new Map();
};

/** 
 * @param {string} key 
 * @param {number} val
 * @return {void}
 */
MapSum.prototype.insert = function(key, val) {
    // 避免重复
    const delta = val - (this.map.get(key) || 0);
    this.map.set(key, val);
    for (let i = 1; i <= key.length; ++i) {
        const currprefix = key.substring(0, i);
        this.prefixmap.set(currprefix, (this.prefixmap.get(currprefix) || 0) + delta);
    }
};

/** 
 * @param {string} prefix
 * @return {number}
 */
MapSum.prototype.sum = function(prefix) {
    return this.prefixmap.get(prefix) || 0;
};
  1. 最大的异或

这个是真的难,思路转换很奇妙

/**
 * @param {number[]} nums
 * @return {number}
 */
var findMaximumXOR = function(nums) {
    const tree={};
    for(let i=0;i<nums.length;i++){
        let node=tree;
        for(let j=30;j>=0;j--){
            let attr=(nums[i]>>j)&1;
            if(!node[attr]){
                node[attr]={};
            }
            node=node[attr];
        }
    }
    let max=0;
    for(let i=0;i<nums.length;i++){
        let node=tree;
        let result=0;
        for(let j=30;j>=0;j--){
            let bit=(nums[i]>>j)&1;
            let index=bit^1;
            if(node[index]){
                result|=(1<<j);
                node=node[index];
            }
            else{
                node=node[bit];
            }
        }
        max=Math.max(max,result);
    }
    return max;
};

总结

前缀树通常用来保存字符串,它的节点和字符串的字符对应,而路径和字符串对应。如果只考虑英文小写字母,那么前缀树的每个节点有26个子节点。为了标注某些节点和字符串的最后一个字符对应,前缀树节点中通常需要一个布尔类型的字段。

前缀树经常用来解决与字符串查找相关的问题。和哈希表相比,在前缀树中查找更灵活。既可以从哈希表中找出所有以某个前缀开头的所有单词,也可以找出修改了一个(或多个)字符的字符串。

使用前缀树解决问题通常需要两步:第1步是创建前缀树,第2步是在前缀树中查找。


看见了
876 声望16 粉丝

前端开发,略懂后台;