力扣刷题-面试题 17.13. 恢复空格、字典树、前缀树的应用

2023-11-15

基本概念

  1. Trie 树

    又称单词查找树、前缀树,是一种树形结构。典型应用是用于统计、排序和保存大量的字符串(但不仅限于字符串)。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,比哈希表更快。

  2. 基本性质

    ①.根节点不包含字符,除根节点外每个节点都只包含一个字符

    ②.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串

    ③.每个节点的所有子节点包含的字符都不相同

  3. 基本操作

    ①.插入:把一个单词插入到字典树

    ②.查询前缀:判断某个单词是否为一个单词的前缀

    ③.查询单词:判断某个单词是否已经存在

基本原理

  1. 字典树的本质

    Trie 树的本质,就是利用字符串之间的公共前缀,将重复的前缀合并在一起。

  2. 构建原理

    Trie 树的插入操作就是将单词的每个字母逐一插入Trie树。插入前先判断字母对应的节点是否存在,存在则移动到下一层继续插入,不存在则创建对应的节点。

实现方法

// TrieNode 节点类,由 a-z 小写字母构成的字典树
class TrieNode
{
private:
    int count;//包含子节点数量,可以用于判断是否叶子节点
    bool isEnd;//标记是否单词结尾
    vector<TrieNode*> children;//存储子节点指针
public:
    // 构造
    TrieNode():count(0),isEnd(false),children(26,NULL) {}
    // 析构
    ~TrieNode()
    {
        for(int i = 0;i < 26;i++ )
        {
            if( children[i] ) delete children[i];
        }
    }
    // 对外系列接口
    int size() { return count ;} // 返回子节点数量
    TrieNode* insertNode(char c) // 插入一个子节点,并返回其指针
    {
        if( c  <  'a' || c > 'z' ) return NULL;
        if( children[c - 'a'] == NULL)
        {
            children[c - 'a'] = new TrieNode();
            count++;
        }
        return children[c - 'a'] ;
    }
    TrieNode* getNode( char c )//返回指定子节点
    {
        if( c  <  'a' || c > 'z' ) return NULL;
        return children[c - 'a'] ;
    }
    bool idWordEnd(){ return isEnd;}//返回是否单词结尾
    void setEnd() { isEnd = true ;}//标记本节点为单词结尾
};
// Trie 类,封装操作接口
class Trie {
private:
    TrieNode * root;//根节点
public:
    // 构造
    Trie() : root( new TrieNode() ){}
    // 析构
    ~Trie()
    {
        delete root;
    }
    // 插入一个单词
    void insert(string word) {
        TrieNode * p = root;
        for(int i = 0;i < word.size();i++ )
        {
            p = p->insertNode(word[i]);
        }
        p->setEnd() ;
    }
    //逆序插入一个单词
    void insertReverse(string word) {
        TrieNode * p = root;
        for(int i = word.size() -1;i >-1;i-- )
        {
            p = p->insertNode(word[i]);
        }
        p->setEnd() ;
    }
    //根据单词返回节点
    TrieNode *getNode(string word)
    {
        TrieNode * p = root;
        for(int i = 0;i < word.size();i++ )
        {
            p = p->getNode(word[i]) ;
            if( p == NULL ) return NULL;
        }
        return p;
    }
    // 判断指定单词是否存在
    bool search(string word) {
        TrieNode * p = getNode(word);
        if( p )
        	return  p->idWordEnd();
        return false;
    }
    //判断指定前缀是否存在
    bool startsWith(string prefix) {
        TrieNode * p = getNode(prefix);
        return p != NULL;
    }
};

字典树应用

你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!“已经变成了"iresetthecomputeritstilldidntboot”。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。

示例:

输入:
dictionary = [“looked”,“just”,“like”,“her”,“brother”]
sentence = “jesslookedjustliketimherbrother”
输出: 7
解释: 断句后为"jess looked just like tim her brother",共7个未识别字符。

来源:力扣(LeetCode)

  1. 题目分析

    ①.动态规划

    定义 dp[i] 表示考虑截止到位置 i 时最少的未识别的字符数量。

    为方便初始化,在字符串开头增加一个不可识别字符 “#”,则dp[0] = 1。

    若存在一个位置 j 把前 i 个字符构成的子串 [0,i] 分为两部分,并且子串 [j,i] 是字典里的单词,如下图所示:

    在这里插入图片描述

    dp[i] 可以转换成 dp[j-1],遍历找到所有的 j ,然后dp[i] 取所有 j 位置的最小值即可,所以状态转移方程为dp[i] = min(dp[i],dp[j-1]);

    若不存在一个位置 j,则 dp[i] = dp[i-1] + 1。

    ②.字典树

    用 j 在范围 [0,i] 遍历所有子串 [j,i] 时,每次都从头到尾截取子串,存在大量的重复判断,可以使用字典树进行优化:
    从 j = i 开始倒叙遍历,若 [j,i] 不是字典是中的前缀,则直接中断循环即可,若 [j,i] 是字典是中的前缀,再判断是否是字典中的单词。

  2. 代码示例

    class TrieNode
    {
    private:
        int count;//包含子节点数量,可以用于判断是否叶子节点
        bool isEnd;//标记是否单词结尾
        vector<TrieNode*> children;//存储子节点指针
    public:
        // 构造
        TrieNode():count(0),isEnd(false),children(26,NULL) {}
        // 析构
        ~TrieNode()
        {
            for(int i = 0;i < 26;i++ )
            {
                if( children[i] ) delete children[i];
            }
        }
        // 对外系列接口
        int size() { return count ;} // 返回子节点数量
        TrieNode* insertNode(char c) // 插入一个子节点,并返回其指针
        {
            if( c  <  'a' || c > 'z' ) return NULL;
            if( children[c - 'a'] == NULL)
            {
                children[c - 'a'] = new TrieNode();
                count++;
            }
            return children[c - 'a'] ;
        }
        TrieNode* getNode( char c )//返回指定子节点
        {
            if( c  <  'a' || c > 'z' ) return NULL;
            return children[c - 'a'] ;
        }
        bool idWordEnd(){ return isEnd;}//返回是否单词结尾
        void setEnd() { isEnd = true ;}//标记本节点为单词结尾
    };
    // Trie 类,封装操作接口
    class Trie {
    private:
        TrieNode * root;//根节点
    public:
        // 构造
        Trie() : root( new TrieNode() ){}
        // 析构
        ~Trie()
        {
            delete root;
        }
        // 插入一个单词
        void insert(string word) {
            TrieNode * p = root;
            for(int i = 0;i < word.size();i++ )
            {
                p = p->insertNode(word[i]);
            }
            p->setEnd() ;
        }
        //逆序插入一个单词
        void insertReverse(string word) {
            TrieNode * p = root;
            for(int i = word.size() -1;i >-1;i-- )
            {
                p = p->insertNode(word[i]);
            }
            p->setEnd() ;
        }
        //根据单词返回节点
        TrieNode *getNode(string word)
        {
            TrieNode * p = root;
            for(int i = 0;i < word.size();i++ )
            {
                p = p->getNode(word[i]) ;
                if( p == NULL ) return NULL;
            }
            return p;
        }
        // 判断指定单词是否存在
        bool search(string word) {
            TrieNode * p = getNode(word);
            if( p )
            	return  p->idWordEnd();
            return false;
        }
        //判断指定前缀是否存在
        bool startsWith(string prefix) {
            TrieNode * p = getNode(prefix);
            return p != NULL;
        }
        //
    };
    class Solution {
    public:
        int respace(vector<string>& dictionary, string sentence) {
            Trie * trie = new Trie();
            for(int i = 0;i < dictionary.size();i++ )
            {
                string word = dictionary[i];
                trie->insertReverse(word);
            }
            sentence = '#'+sentence;
            vector<int> dp(sentence.size(),0);
            dp[0] = 1;
            for( int i = 1;i < sentence.size();i++)
            {
                dp[i] = dp[i-1]+1;
                string temp = "";
                for(int j = i;j > -1;j--)
                {
                    temp += sentence[j] ;
                    TrieNode * p = trie->getNode(temp);
                    if( p  ) //是后缀
                    {
                        if( p->idWordEnd() )
                            dp[i] = min(dp[i],dp[j-1]);
                    }
                    else
                    {
                        break;
                    }
                }
            }
            return dp[sentence.size()-1] -1 ;
        }
    };
    

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

力扣刷题-面试题 17.13. 恢复空格、字典树、前缀树的应用 的相关文章

随机推荐

  • HDU - 1020 Encoding

    Given a string containing only A Z we could encode it using the following method Each sub string containing k same chara
  • IDEA 安装插件IDE Eval Reset

    IDE Eval Reset是什么 idea eval reset是Jetbrains的插件 官方良心产品 会允许我们试用30天 可以借此重新刷新idea正版程序的使用期限 哈哈哈 爽到没朋友 具体操作 1 点击intelliJ IDEA
  • [开源协议]58种开源协议及分类

    转载自 http www opensource org licenses alphabetical 更多关于具体协议内容请看其链接 Licenses that are popular and widely used or with stro
  • Linux、Ubuntu下安装yaml, 关于Import Error: No module named yaml

    pip install pyyaml 如果不行的话 就 conda install yaml 最后 gt gt gt import yaml 没有报错就成功了
  • mingw64镜像网站

    mingw64镜像网站 http files 1f0 de mingw
  • UIBOT的简单使用

    最近项目上使用到一个新的技术软件 刚一阶段使用结束 用来记录下 首先我们了解下UIbot 这里我直接放上下载社区版本的官方地址 来也科技RPA AI智能自动化平台 助力政企实现智能时代的人机协同 首先需要用邮箱注册 然后直接安装社区版本 这
  • 【毕设教程】FCM模糊聚类算法

    文章目录 0 前言 1 如何理解模糊聚类 2 模糊C means聚类算法 3 FCM算法原理 4 Python FCM支持 4 1 安装相关库 4 2 skfuzzy cmeans函数说明 4 3 代码实现 4 4 运行结果 5 FCM算法
  • C++stringstream的简单介绍以及使用

    在C语言中 如果想要将一个整形变量的数据转化为字符串格式可以使用以下两种方式 1 itoa 函数 2sprint 函数 但是两个函数在转化时 都得需要先给出保存结果的空间 那空间要给多大呢 就不太好界定 而且转化格式不匹配时 可能还会得到错
  • matlab打开视频文件并提取颜色数据

    目标 实现加载任意视频文件 并按帧取指定图像区域的某颜色值代表该区域的颜色值 1 加载视频文件 加载视频文件使用函数VideoReader 输入为文件夹路径 返回为一个VideoReader对象 具体使用方法见创建对象以读取视频文件 MAT
  • 离散数学主析取范式及主合取范式

    今天总结了一下关于离散数学化简主析取范式以及主合取范式的一些方法 首先一般可能会用到 分配律 A B C lt gt A B A C A B C lt gt A B A C 其次若化简式里有蕴涵符号 则可以用 蕴涵等值式 A B lt gt
  • 数据清洗、数据挖掘常见十大问题

    数据清洗 数据挖掘常见十大问题 一 数据预处理 数据清洗和特征工程 二 数据预处理和特征工程阶段 最常见的10个问题 1 什么是数据 EDA 2 缺失值的处理方式有哪些 3 如何检测异常数据 如何处理 4 什么是特征工程 有什么作用 5 特
  • 【Spring】数据导出为Excel的接口报java.io.IOException: UT010029: Stream is closed错误

    数据导出为Excel的接口报java io IOException UT010029 Stream is closed错误 实习时导师让写一个平台信息导出为Excel的功能 写完之后发现文件正常导出 但控制台一直报Stream is clo
  • react中使用less和全局样式

    前言 使用create react app脚手架搭建的react项目 会自带css和sass 但是没有less 如果在项目中需要使用less 需要进行下载并进行一些配置 1 配置 1 暴露webpack配置文件 create react a
  • 解决 in ./node_modules/cesium/Source/ThirdParty/zip.js报错

    由于在 node modules cesium Source ThirdParty zip js 文件中使用了 import meta 语法 webpack 默认不支持 在进行项目构建时 会报如下错误 提示信息需要添加 loader 接下来
  • 谷歌浏览器配置微信浏览器_使用Chrome修改user agent模拟微信内置浏览器

    很多时候 我们需要模拟微信内置浏览器 今天教大家用chrome简单模拟 如图设置 F12或者右键审查元素进入开发者模式 点击Emulation 然后点击Network 把Spoof user agent改成Other 并把下面的带复制进去
  • PaddleSpeech调研、安装、使用

    PaddleSpeech概述 PaddleSpeech asr 模块目前只支持中英文的语音自动识别 建议在Linux环境下安装和使用 配置环境要求 gcc gt 4 8 5 paddlepaddle gt 2 4 1 python gt 3
  • 概率论与数理统计

    目录 一 概率论的基本概念 1 1 概率论的直观解释和数学定义 1 2 条件概率与乘法公式 1 3 全概率公式与贝叶斯公式 1 4 事件的独立性 二 随机变量与分布函数 2 1 随机变量与分布函数 2 2 离散型随机变量和常用分布 2 3
  • 定时任务——Cron表达式详解

    Cron表达式是一个字符串 字符串以5或6个空格隔开 分为6或7个域 每一个域代表一个含义 Cron有如下两种语法格式 Seconds Minutes Hours DayofMonth Month DayofWeek Year或 Secon
  • C++ : 在一个string字符串中查找给定的字符串并提取

    C 在一个string字符串中查找给定的字符串并提取 1 string find last of 返回类型 size t 2 string find first of 返回类型 size t 3 string substr size t a
  • 力扣刷题-面试题 17.13. 恢复空格、字典树、前缀树的应用

    基本概念 Trie 树 又称单词查找树 前缀树 是一种树形结构 典型应用是用于统计 排序和保存大量的字符串 但不仅限于字符串 它的优点是 利用字符串的公共前缀来减少查询时间 最大限度地减少无谓的字符串比较 比哈希表更快 基本性质 根节点不包