C++实现LeetCode(642.设计搜索自动补全系统)

下面是C++实现LeetCode设计搜索自动补全系统(642题)的完整攻略。

问题描述

实现一个搜索自动补全系统,可以支持以下功能:

  1. 给定一个字符串prefix,返回所有下一个可能的查询已经它们的出现次数,按照次数排列(降序);
  2. 插入一个句子sentence时,插入这个句子的所有前缀。
  3. 输入的所有字符串都只包含小写字母,且长度不会超过1000。

示例:

输入:["AutocompleteSystem","input","input","input","input"]
     [[["i love you","island","iroman","i love leetcode"],[5,3,2,2]],["i"], [" "], ["a"], ["#"]]
输出: [null, ["i love you","island","i love leetcode"],[],["i love you","i love leetcode"],[]]

其中,输入的"#"表示句子已经结束。

解决思路

要解决这个问题,我们可以使用Trie树结构。Trie树是一种多叉树,用于存储一组字符串。

具体地,我们设立一个Trie树节点类,类成员包括当前字符和子节点数组(大小为26)等,然后设计自动补全的类,成员包括Trie树的根节点、当前搜索的字符串、以及保存搜索结果的vector等。

接下来我们简单分析一下自动补全的过程:

插入一个句子sentence时,遍历sentence中的所有前缀,将其放入Trie树中。
给定一个字符串prefix时,对于Trie树中以prefix作为前缀的最后一个节点进行搜索,然后执行DFS遍历从这个节点的所有子节点开始的子树,这样就可以得到所有的可能搜索以及它们的出现次数。

代码实现

下面是代码实现,完整的代码实现请见:https://github.com/huihuimaizi/LeetCode-642-Design-Search-Autocomplete-System。

// Trie节点
class TrieNode {
public:
    unordered_map<char, TrieNode*> children;  // 子节点数组
    int freq;  // 记录当前前缀的出现次数
    bool isEnd;  // 标记是不是字符串结尾

    TrieNode() : freq(0), isEnd(false) {}
};

// Trie树
class Trie {
public:
    Trie() {
        root = new TrieNode();
    }

    void insert(const string& s, int freq) {
        TrieNode* node = root;
        for (const char& c : s) {
            if (node->children.count(c) == 0) {
                node->children[c] = new TrieNode();
            }
            node = node->children[c];
        }
        node->freq += freq;
        node->isEnd = true;
    }

    void dfs(TrieNode* node, string& path, vector<pair<string, int>>& v) {
        if (node->isEnd) {
            v.emplace_back(path, node->freq);
        }
        for (const auto& kv : node->children) {
            path.push_back(kv.first);
            dfs(kv.second, path, v);
            path.pop_back();
        }
    }

    TrieNode* getRoot() const {
        return root;
    }
private:
    TrieNode* root;
};

class AutocompleteSystem {
public:
    AutocompleteSystem(vector<string>& sentences, vector<int>& times) {
        for (int i = 0; i < sentences.size(); ++i) {
            trie.insert(sentences[i], times[i]);
        }
    }

    vector<string> input(char c) {
        if (c == '#') {
            trie.insert(current_str, 1);
            current_str = "";
            return {};
        }
        current_str.push_back(c);
        vector<pair<string, int>> v;
        trie_node = trie_node ? trie_node->children[c] : trie.getRoot();
        if (trie_node) {
            string path;
            dfsForSearch(trie_node, path, v);
        }
        sort(v.begin(), v.end(), [](const auto& p1, const auto& p2) { return p1.second > p2.second || (p1.second == p2.second && p1.first < p2.first); });
        vector<string> res;
        for (int i = 0; i < min((int)v.size(), 3); ++i) {
            res.emplace_back(current_str + v[i].first);
        }
        return res;
    }
private:
    Trie trie;  // Trie树
    string current_str;  // 当前搜索的字符串
    TrieNode* trie_node = nullptr;  // 当前搜索的节点

    void dfsForSearch(TrieNode* node, string& path, vector<pair<string, int>>& v) {
        if (node->isEnd) {
            v.emplace_back(path, node->freq);
        }
        for (const auto& kv : node->children) {
            path.push_back(kv.first);
            dfsForSearch(kv.second, path, v);
            path.pop_back();
        }
    }
};

测试案例

下面是两条测试案例:

案例1

输入:

["AutocompleteSystem","input","input","input","input"]
[[["i love you","island","iroman","i love leetcode"],[5,3,2,2]],["i"],[" "],["a"],["#"]]

输出:

[null, ["i love you","island","i love leetcode"],[],["i love you","i love leetcode"],[]]

案例2

输入:

["AutocompleteSystem","input","input","input","input","input","input","input","input","input"]
[[["abc","abbc","aacc","bbcc"],[3,2,1,1]],["b"],["c"],["#"],["b"],["c"],["#"],["a"],["b"],["c"],["#"]]

输出:

[null,[],[],[],["bc"], ["bc"],["b"],["bc","bbcc","abbc"],["bc","bbcc"],["bc","bbcc","abbc","aacc"]]

总结

在这个问题中,Trie树是解决方法的核心。通过将前缀字符串存储在Trie树中,我们可以实现输入时的搜索以及自动补全输出。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++实现LeetCode(642.设计搜索自动补全系统) - Python技术站

(0)
上一篇 2023年5月23日
下一篇 2023年5月23日

相关文章

  • C/C++百行代码实现热门游戏消消乐功能的示例代码

    C/C++百行代码实现热门游戏消消乐功能的示例代码攻略 简介 消消乐是一款非常流行的益智游戏,其核心游戏玩法是三消规则,在有限的步数内将相同颜色(或形状)的方块消除。本文将通过C/C++语言编写少于100行代码来实现消消乐游戏功能。 实现步骤 第一步:定义方块 我们需要定义游戏中的方块,方块应该包含颜色、形状以及消除状态等属性。具体实现如下: struct …

    C 2023年5月24日
    00
  • C语言之结构体定义 typedef struct 用法详解和用法小结

    C语言之结构体定义 typedef struct 用法详解和用法小结 在C语言中,结构体是一种自定义的数据类型,它可以包含多个不同类型的变量,并被视为一个整体。但是,直接定义结构体并不方便,因此可以使用typedef struct来定义结构体类型,使代码更加简洁和易读。 typedef struct的基本用法 typedef struct的语法格式为: ty…

    C 2023年5月22日
    00
  • 基于条件变量的消息队列 说明介绍

    基于条件变量的消息队列是一种进程间通信机制,适用于多线程环境。在使用过程中,需要注意线程同步和互斥的问题。 什么是条件变量 条件变量是线程间同步的一种方式,线程可以调用它的wait()方法将自己阻塞,直到其他线程调用signal()方法才能重新运行。条件变量常和互斥锁配合使用,锁用来保护数据,条件变量用来等待特定条件的发生。 消息队列 消息队列是一种消息传递…

    C 2023年5月22日
    00
  • C/C++ 活动预处理器详解

    下面是对C/C++预处理器的详细讲解: C/C++预处理器简介 C/C++预处理器是C/C++编译过程中的一个重要环节,其作用是在编译之前对源代码进行处理解析,可以理解为是一种对源代码进行预处理的程序。C/C++预处理器用于在编译之前对源代码进行简单的替换和操作,以便更好地对源代码进行编译和调试。 C/C++预处理器主要有以下几个作用: 头文件包含:将头文件…

    C 2023年5月23日
    00
  • 结构体的(.)操作符和(->)操作符区别

    一、结构体的 . 操作符二、结构体的 -> 操作符三、点操作符的优先性和结合性四、总结 一、结构体的 .操作符 1.结构体成员的直接访问:结构体变量的成员是通过操作符 . 访问的。 二、结构体的->操作符 1.结构体成员的间接访问:当我们拥有一个 指向结构体的指针 ,我们访问这个结构的成员的方式是 对指针执行间接访问操作 ,然后再通过 点操作符 …

    C语言 2023年4月18日
    00
  • C语言如何用顺序栈实现回文序列判断

    C语言可以利用顺序栈来实现回文序列的判断,下面是实现的完整攻略。 什么是回文序列? 回文序列是一个正读与反读都相同的序列,例如:121, abccba。 用顺序栈实现回文序列判断 算法思路 回文序列的判断可以利用栈的先进后出的特性,我们可以将序列的前一半依次入栈,后一半依次和栈中元素进行出栈比较。如果每次比较都相等,则说明是回文序列。 代码实现 下面是C语言…

    C 2023年5月23日
    00
  • C中静态变量和寄存器变量的区别

    首先我们来看一下C语言中静态变量和寄存器变量的区别。 静态变量 定义 静态变量是指在函数或者代码块中定义的变量,其生命周期和程序的运行周期相同,不会在作用域结束后立刻销毁。 初始化 静态变量默认初始化为0。 作用域 静态变量的作用域与具体定义位置相关: 在代码块中定义的静态变量,它的作用域是该代码块; 在函数中定义的静态变量,它的作用域是整个函数。 不同源文…

    C 2023年5月10日
    00
  • C++实现编码转换的示例代码

    对于C++编码转换,通常使用的是C++11提供的codecvt头文件中的codecvt_utf8和codecvt_utf16模板类,这两个模板类可以帮助我们进行不同编码之间的转换。下面是一个完整的示例代码: #include <iostream> #include <locale> #include <codecvt> i…

    C 2023年5月24日
    00
合作推广
合作推广
分享本页
返回顶部