力扣刷题-面试题 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. 恢复空格、字典树、前缀树的应用 的相关文章

  • C 编程 - 文件 - fwrite

    我有一个关于编程和文件的问题 while current NULL if current gt Id Doctor 0 current current gt next id doc current gt Id Doctor if curre
  • 以文化中立的方式将字符串拆分为单词

    我提出了下面的方法 旨在将可变长度的文本拆分为单词数组 以进行进一步的全文索引处理 删除停止词 然后进行词干分析 结果似乎不错 但我想听听关于这种实现对于不同语言的文本的可靠性的意见 您会建议使用正则表达式来代替吗 请注意 我选择不使用 S
  • 为什么 C# Array.BinarySearch 这么快?

    我已经实施了一个很简单用于在整数数组中查找整数的 C 中的 binarySearch 实现 二分查找 static int binarySearch int arr int i int low 0 high arr Length 1 mid
  • WCF RIA 服务 - 加载多个实体

    我正在寻找一种模式来解决以下问题 我认为这很常见 我正在使用 WCF RIA 服务在初始加载时将多个实体返回给客户端 我希望两个实体异步加载 以免锁定 UI 并且我想利用 RIA 服务来执行此操作 我的解决方案如下 似乎有效 这种方法会遇到
  • 为什么两个不同的 Base64 字符串的转换会返回相等的字节数组?

    我想知道为什么从 base64 字符串转换会为不同的字符串返回相同的字节数组 const string s1 dg const string s2 dq byte a1 Convert FromBase64String s1 byte a2
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 如何使用 ICU 解析汉字数字字符?

    我正在编写一个使用 ICU 来解析由汉字数字字符组成的 Unicode 字符串的函数 并希望返回该字符串的整数值 五 gt 5 三十一 gt 31 五千九百七十二 gt 5972 我将区域设置设置为 Locale getJapan 并使用
  • 在 Windows 窗体中保存带有 Alpha 通道的单色位图会保存不同(错误)的颜色

    在 C NET 2 0 Windows 窗体 Visual Studio Express 2010 中 我保存由相同颜色组成的图像 Bitmap bitmap new Bitmap width height PixelFormat Form
  • 在 ASP.NET 5 中使用 DI 调用构造函数时解决依赖关系

    Web 上似乎充斥着如何在 ASP NET 5 中使用 DI 的示例 但没有一个示例显示如何调用构造函数并解决依赖关系 以下只是众多案例之一 http social technet microsoft com wiki contents a
  • C# 中通过 Process.Kill() 终止的进程的退出代码

    如果在我的 C 应用程序中 我正在创建一个可以正常终止或开始行为异常的子进程 在这种情况下 我通过调用 Process Kill 来终止它 但是 我想知道该进程是否已退出通常情况下 我知道我可以获得终止进程的错误代码 但是正常的退出代码是什
  • 带动态元素的 WPF 启动屏幕。如何?

    我是 WPF 新手 我需要一些帮助 我有一个加载缓慢的 WPF 应用程序 因此我显示启动屏幕作为权宜之计 但是 我希望能够在每次运行时更改屏幕 并在文本区域中显示不同的引言 这是一个生产力应用程序 所以我将使用非愚蠢但激励性的引言 当然 如
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 如何在 C 中调用采用匿名结构的函数?

    如何在 C 中调用采用匿名结构的函数 比如这个函数 void func struct int x p printf i n p x 当提供原型的函数声明在范围内时 调用该函数的参数必须具有与原型中声明的类型兼容的类型 其中 兼容 具有标准定
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • 为什么编译时浮点计算可能不会得到与运行时计算相同的结果?

    In the speaker mentioned Compile time floating point calculations might not have the same results as runtime calculation
  • 混合 ExecutionContext.SuppressFlow 和任务时 AsyncLocal.Value 出现意外值

    在应用程序中 由于 AsyncLocal 的错误 意外值 我遇到了奇怪的行为 尽管我抑制了执行上下文的流程 但 AsyncLocal Value 属性有时不会在新生成的任务的执行范围内重置 下面我创建了一个最小的可重现示例来演示该问题 pr
  • IEnumreable 动态和 lambda

    我想在 a 上使用 lambda 表达式IEnumerable
  • 如何将服务器服务连接到 Dynamics Online

    我正在修改内部管理应用程序以连接到我们的在线托管 Dynamics 2016 实例 根据一些在线教程 我一直在使用OrganizationServiceProxy out of Microsoft Xrm Sdk Client来自 SDK
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写
  • 使用.NET技术录制屏幕视频[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有没有一种方法可以使用 NET 技术来录制屏幕 无论是桌面还是窗口 我的目标是免费的 我喜欢小型 低

随机推荐

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