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语言常用的异常处理模拟方法是使用一些特殊的返回值来表示程序的不同状态。例如,某个函数正常执行时返回0,当函数执行出错时返回其他值。这种方式是可以扩展的,我们可以自定义一些特殊的返回值,来表示不同的异常情况…

    C 2023年5月22日
    00
  • C++ 再识类和对象

    C++中,对象是一种特别的变量,它是类的一个实例。类是一种定义对象的模板,它包括数据和各种方法。在本篇文章中,我们将会详细讲解C++中的类和对象,让你更好地理解它们的用法和原理。 定义类 C++是一种面向对象的编程语言,其中类是面向对象的一个基本概念。我们可以使用以下方式定义一个类: class Student { public: //公有的成员函数 voi…

    C 2023年5月22日
    00
  • C++日期和时间编程小结

    C++日期和时间编程小结完整攻略 本文将介绍使用C++编程语言来获取和处理日期和时间的相关技巧和知识。首先,我们需要了解C++标准库中关于日期和时间的头文件<chrono>和<ctime>。 头文件介绍 头文件\ 在C++11标准中,引入了一个新的日期和时间库<chrono>,它提供了丰富的日期和时间操作工具。通过<…

    C 2023年5月23日
    00
  • 关于C/C++中可变参数的详细介绍(va_list,va_start,va_arg,va_end)

    关于C/C++中可变参数的详细介绍,一般涉及到四个主要的宏,它们分别是va_list,va_start,va_arg和va_end。下面我会详细介绍它们的用法和注意事项,并且提供两个示例。 1. va_list va_list是一个类型,用于存储可变参数的信息。声明方式如下: #include <stdarg.h> va_list arg_lis…

    C 2023年5月23日
    00
  • 如何通过C++求出链表中环的入口结点

    1. 环的入口结点(题目描述) 给定一个链表,返回链表中环的入口结点。如果链表无环,则返回 NULL。 要求算法的空间复杂度为 O(1)。 2. 思路分析 这道题可以使用双指针法(快慢指针)来解决。 具体的思路为:首先,设定两个指针,分别为 fast 和 slow,然后,让它们以不同的速度往前走(fast 比 slow 快),这样,当两个指针重合时,就表示链…

    C 2023年5月23日
    00
  • php 输出json及显示json中的中文汉字详解及实例

    下面是“PHP输出JSON并显示JSON中的中文汉字”的详细攻略: 什么是JSON? JSON,全称为JavaScript Object Notation,是一种轻量级的数据交换格式。它采用键值对,数据易于读写和解析。在Web应用中传递数据时,JSON已成为事实上的标准,很多互联网公司的API都是以JSON格式输出数据。 为什么需要输出JSON? 在Web应…

    C 2023年5月23日
    00
  • C语言小项目计时器的实现思路(倒计时+报警提示)

    C语言小项目计时器的实现思路(倒计时+报警提示) 思路概括 计时器的实现思路可以分为三个部分: 用户输入倒计时的时间,程序将其保存下来。 程序不断地循环检查当前时间与开始时间之间的差值是否大于等于用户设定的时间,当差值达到要求时,触发报警提示。 用户可以选择中途取消倒计时。 具体实现 1. 用户输入倒计时的时间 用户需输入倒计时的时间,可以通过scanf函数…

    C 2023年5月23日
    00
  • C++中基类和派生类之间的转换实例教程

    C++中基类和派生类之间的转换实例教程 什么是基类和派生类呢? 在C++中,基类和派生类是面向对象编程中的两个基本概念。基类通常是一个抽象的概念,它定义了一些通用的特征,在派生类中被继承和扩展。派生类则是从基类派生出来的类,它继承了基类的特性,并在此基础上增加了一些自己的特性。 转换示例 我们来看一个实际的示例,假设现在我们有一个基类People,和一个派生…

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