745. 前缀和后缀搜索

题目描述

设计一个包含一些单词的特殊词典,并能够通过前缀和后缀来检索单词。

实现 WordFilter 类:

  • WordFilter(string[] words) 使用词典中的单词 words 初始化对象。
  • f(string pref, string suff) 返回词典中具有前缀 prefix 和后缀 suff 的单词的下标。如果存在不止一个满足要求的下标,返回其中 最大的下标 。如果不存在这样的单词,返回 -1 。

提示:

  • 1 <= words.length <= 104
  • 1 <= words[i].length <= 7
  • 1 <= pref.length, suff.length <= 7
  • words[i]、pref 和 suff 仅由小写英文字母组成
  • 最多对函数 f 执行 104 次调用

示例:

输入
["WordFilter","f"]
[[["apple","apply","ale"]],["app","le"]]
输出
[null, 1]

 

解题思路:

1.字典树

  • 创建两个字典树 pref_root 和 suff_root ,pref_root用来保存单词前缀,suff_root用来保存单词后缀。
  • 树的根设为Map(),每个节点的结构如下:
    • key:保存字符;
    • value:自定义的Node类型
      • index:列表,保存包含该前缀的单词索引
      • children:自定义Node类型,指向下一个前缀
  • 搜索时,分别返回前缀树中匹配的前缀位置的index列表 list1 与后缀树中匹配的后缀位置的index列表 list2 ;若找不到匹配的前缀,返回的是 null 。
  • 由于建树时,index保存的索引已经是从小到大排列,因此从后往前搜索 lis1 与 list2 ,找到两者相同的索引即是最大下标。

 

补充:一开始仅将index的类型设为一个数,认为每次加入新单词时,更新 index  为当前单词下标即可得到最大下标。但要注意的是,有可能出现前缀匹配的单词索引与后缀匹配的单词索引不一致的情况,而这样做得不到正确的最大下标。因此改为Array类型。

 

代码实现:

复制代码
/**
 * @param {string[]} words
 */
var WordFilter = function(words) {
    function Node(list,map){
        this.index = list;
        this.children = map;
    }
    this.pref_root = new Map(); //前缀树
    this.suff_root = new Map(); //后缀树
    for(let i=0;i<words.length;i++){
        let len = words[i].length;
        let pref_cur = this.pref_root;
        let suff_cur = this.suff_root;
        for(let j=0;j<len;j++){
            let letter = words[i][j];

            
            if(pref_cur.has(letter)){//前缀已在字典中时,将当前单词索引加入该节点的index,表示当前单词包含此前缀
                let node = pref_cur.get(letter);
                node.index.push(i);
                pref_cur = node.children;
            }else{//字典中不存在该前缀则创建新的Node
                let node = new Node(new Array(),new Map());
                node.index.push(i);
                pref_cur.set(letter,node);
                pref_cur = node.children;
            }
        }
        for(let j=len-1;j>=0;j--){
            let letter = words[i][j];
            if(suff_cur.has(letter)){
                let node = suff_cur.get(letter);
                node.index.push(i);
                suff_cur = node.children;
            }else{
                let node = new Node(new Array(),new Map());
                node.index.push(i);
                suff_cur.set(letter,node);
                suff_cur = node.children;
            }
        }
    }
};

/** 
 * @param {string} pref 
 * @param {string} suff
 * @return {number}
 */
WordFilter.prototype.f = function(pref, suff) {
    function search(str,root){
        let cur = root;
        let i=0;
        while(i<str.length){
            if(cur.has(str[i])){
                if(i==str.length-1){//当搜索到最后一个字符,直接返回index列表,表示包含该前缀的所有单词索引
                    return cur.get(str[i]).index;
                }else{
                    cur = cur.get(str[i]).children;
                    i++;
                }
            }else{
                return null;
            }
        }
    }
    let list1 = search(pref,this.pref_root);
    let list2 = search(suff.split('').reverse().join(''),this.suff_root);//注意,要将后缀字符串翻转进行搜索

    //当其中一个列表为空,表示前缀或后缀在字典树中匹配不到
    if(list1==null||list2==null){
        return -1;
    }else{
        //从后往前进行搜索,找到两者相同的索引,表示该单词同时满足题目的前缀和后缀
        for(let i=list1.length-1,j=list2.length-1;i>=0&&j>=0;){
            if(list1[i]==list2[j]){
                return list1[i];
            }else if(list1[i]>list2[j]){
                i--;
            }else{
                j--;
            }
        }
        return -1;
    }
};

/**
 * Your WordFilter object will be instantiated and called as such:
 * var obj = new WordFilter(words)
 * var param_1 = obj.f(pref,suff)
 */
复制代码

 

示例中的字典树如下

 

 

 

 

  搜索前缀 "app" ,定位到 pref_root 中第三层字符 " p ",提取出index=[0,1];搜索后缀"le",定位到suff_root中第二层字符 " l ",提取出Index=[0,2]。因此两个列表的相同最大下标为:0。

 

 

2.暴力解法

  枚举出每个单词前缀和后缀组合的可能性,前缀与后缀用特殊符号连接,连接后的字符串作为键,对应的下标作为值保存到哈希表中。当后面单词的前后缀组合在前面出现过,则更新该组合的下标。由于单词是从前往后遍历的,因此可以保证更新后的下标必然是最大下标。

 

代码实现:

复制代码
/**
 * @param {string[]} words
 */
var WordFilter = function(words) {
    this.dict = new Map();
    let index = 0;
    for(const word of words){
        let len = word.length;
        let pref = '';
        for(let i=0;i<len;i++){
            pref += word[i];
            let suff = '';
            for(let j=len-1;j>=0;j--){
                suff = word[j] + suff;//从后往前遍历时,新字符应该添加到旧后缀的前面
                this.dict.set(pref+'+'+suff,index);
            }
        }
        index++;
    }
};

/** 
 * @param {string} pref 
 * @param {string} suff
 * @return {number}
 */
WordFilter.prototype.f = function(pref, suff) {
    let str = pref+'+'+suff;
    if(this.dict.has(str)){
        return this.dict.get(str);
    }else{
        return -1;
    }
};

/**
 * Your WordFilter object will be instantiated and called as such:
 * var obj = new WordFilter(words)
 * var param_1 = obj.f(pref,suff)
 */
复制代码

 

以apple为例,所有的前后缀组合为("+"号后面字符串为后缀):

 

 

 

posted @   ˙鲨鱼辣椒ゝ  阅读(146)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示