【算法专题训练】36、前缀树路径和

1、LCR 065. 单词的压缩编码

题目信息:

cpp 复制代码
单词数组 words 的 有效编码 由任意助记字符串 s 和下标数组 indices 组成,且满足:

words.length == indices.length
助记字符串 s 以 '#' 字符结尾
对于每个下标 indices[i] ,s 的一个从 indices[i] 开始、到下一个 '#' 字符结束(但不包括 '#')的 子字符串 恰好与 words[i] 相等
给定一个单词数组 words ,返回成功对 words 进行编码的最小助记字符串 s 的长度 。

示例 1:
输入:words = ["time", "me", "bell"]
输出:10
解释:一组有效编码为 s = "time#bell#" 和 indices = [0, 2, 5] 。
words[0] = "time" ,s 开始于 indices[0] = 0 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"
words[1] = "me" ,s 开始于 indices[1] = 2 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"
words[2] = "bell" ,s 开始于 indices[2] = 5 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"

示例 2:
输入:words = ["t"]
输出:2
解释:一组有效编码为 s = "t#" 和 indices = [0] 。

提示:
1 <= words.length <= 2000
1 <= words[i].length <= 7
words[i] 仅由小写字母组成

解题思路:

  • 1、审题:
  • 输入一个字符串数组,返回压缩后的编码字符串的长度,编码规则是每个单词以#结尾拼接,当遇到相同子字符串和其他字符串相同情况时合并一处记录
  • 2、解题:
  • 因为题目的编码规则是单词与子字符串相同则合并一处,并且结果只要求返回编码后的字符串长度,可以变换成两个单词的前缀相同即可,
  • 在构建前缀树时,根据单次的逆序字符创建前缀树,
  • 并进行dfs深度优先遍历,直到叶子结点,使用一个全局变量记录整个路径上的结点个数。

代码实现:

cpp 复制代码
/**
 * 构建前缀树
 * - 以单词的逆序遍历添加节点
 */
TrieNode *buildTrie(vector<string> &words)
{
    TrieNode *root = new TrieNode();
    TrieNode *node = nullptr;
    for (auto word : words)
    {
        node = root;
        // 逆序遍历单个单词
        for (int i = word.length() - 1; i >= 0; --i)
        {
            int pos = word[i] - 'a';
            if (node->children[pos] == nullptr)
            {
                node->children[pos] = new TrieNode();
            }
            node = node->children[pos];
        }
        node->isWord = true;
    }
    return root;
}

/**
 * 深度优先遍历,将每个路径的结点个数添加到path中
 * @param node 当前遍历到的结点位置
 * @param path 遍历到当前结点,到根节点,这条路径的结点个数
 * @param pathSum 所有路径的结点和,要求当遍历到叶子结点时,这个数据才会增加
 *
 */
void dfs(TrieNode *node, int path, int &pathSum)
{
    // 遍历前缀树节点值,一直到叶子结点
    bool isLeaf = true; // 判断当前遍历到的结点是否是叶子结点
    for (int i = 0; i < 26; i++)
    {
        if (node->children[i] != nullptr)
        {
            isLeaf = false;
            dfs(node->children[i], path + 1, pathSum);
        }
    }
    if (isLeaf)
    {
        pathSum += path;
    }
}

int minimumLengthEncoding(vector<string> &words)
{
    TrieNode *root = buildTrie(words);
    root->print();
    int pathSum = 0;
    dfs(root, 1, pathSum);
    return pathSum;
}

2、LCR 066. 键值映射

题目信息:

cpp 复制代码
实现一个 MapSum 类,支持两个方法,insert 和 sum:
MapSum() 初始化 MapSum 对象
void insert(String key, int val) 插入 key-val 键值对,字符串表示键 key ,整数表示值 val 。如果键 key 已经存在,那么原来的键值对将被替代成新的键值对。
int sum(string prefix) 返回所有以该前缀 prefix 开头的键 key 的值的总和。

示例:
输入:
inputs = ["MapSum", "insert", "sum", "insert", "sum"]
inputs = [[], ["apple", 3], ["ap"], ["app", 2], ["ap"]]
输出:
[null, null, 3, null, 5]
解释:
MapSum mapSum = new MapSum();
mapSum.insert("apple", 3);
mapSum.sum("ap");           // return 3 (apple = 3)
mapSum.insert("app", 2);
mapSum.sum("ap");           // return 5 (apple + app = 3 + 2 = 5)

提示:
1 <= key.length, prefix.length <= 50
key 和 prefix 仅由小写英文字母组成
1 <= val <= 1000
最多调用 50 次 insert 和 sum

解题思路:

  • 1、审题:实现一个类,包含两个方法,一个插入insert方法,参数是一个字符串和对应的数字val
  • 还有一个是搜索方法sum,参数也是字符串,返回以该字符串为前缀的所欲单词对应的数字总和
  • 2、解题:
  • 创建前缀树节点,每个节点保存一个对应的数字value,当插入一个完整的单词最后的结点时,设置他的value值为对应数字
  • 调用sum方法时,从根节点开始查找到前缀末尾的结点,然后深度遍历该结点为前缀的所有结点值的和

代码实现:

cpp 复制代码
class TrieNode
{
public:
    TrieNode()
    {
        for (int i = 0; i < 26; i++)
        {
            children[i] = nullptr;
        }
        value = 0;
    };
    ~TrieNode()
    {
        for (int i = 0; i < 26; i++)
        {
            delete children[i];
            children[i] = nullptr;
        }
    };

public:
    TrieNode *children[26];
    int value;
};

class MapSum
{
public:
    MapSum()
    {
        root = new TrieNode();
    }

    /**
     * 前缀树插入单词
     * - 遍历单词所有字符,和前缀树的结点,判断结点是否存在,不存在则创建
     * - 最后结点value属性进行赋值
     */
    void insert(string key, int val)
    {
        TrieNode *node = root;
        for (int i = 0; i < key.length(); i++)
        {
            int pos = key[i] - 'a';
            if (node->children[pos] == nullptr)
            {
                node->children[pos] = new TrieNode();
            }
            node = node->children[pos];
        }
        node->value = val;
    }

    /**
     * 1、先查找到已prefix字符串为前缀树的最后的一个节点
     * 2、然后dfs深度优先遍历,所有的单词,并返回所有的结点累计和
     */
    int sum(string prefix)
    {
        TrieNode *node = root;
        for (int i = 0; i < prefix.length(); i++)
        {
            int pos = prefix[i] - 'a';
            if (node->children[pos] != nullptr)
            {
                node = node->children[pos];
            }
            else
            {
                return 0; // 不存在以该字符串为前缀的结点
            }
        }

        return getSum(node);
    }

    int getSum(TrieNode *node)
    {
        if (node == nullptr)
        {
            return 0;
        }
        int sum = node->value;
        for (int i = 0; i < 26; i++)
        {
            if (node->children[i] != nullptr)
            {
                sum += getSum(node->children[i]);
            }
        }
        return sum;
    }

private:
    TrieNode *root;
};

3、总结

  • 前缀树,是需要将字符串拆成单个字符,然后以该字符为标准插入对应位置的结点,如果题目是以后缀为对齐方式,可以转变为以前缀方式对齐,然后构建前缀树进行求解
  • 插入字符,并设置节点数据,查询以某个字符为前缀的结点数据之和,同样使用前缀树,先找到以该字符串为前缀的结点,然后深度优先遍历去找到所有的结点数值
相关推荐
POLITE32 小时前
Leetcode 42.接雨水 JavaScript (Day 3)
javascript·算法·leetcode
好易学·数据结构2 小时前
可视化图解算法76:最大子数组和
数据结构·算法·leetcode·面试·动态规划·力扣·笔试
副露のmagic2 小时前
更弱智的算法学习 day13
学习·算法
青岛少儿编程-王老师2 小时前
CCF编程能力等级认证GESP—C++1级—20251227
java·c++·算法
Sylus_sui3 小时前
git中如何从某次历史提交节点上创建一个新的分支
git·算法·哈希算法
nn在炼金3 小时前
大模型领域负载均衡技术
人工智能·算法·负载均衡
falldeep3 小时前
Pandas入门指南
数据结构·算法·leetcode·pandas
natide3 小时前
表示/嵌入差异-4-闵可夫斯基距离(Minkowski Distance-曼哈顿距离-欧氏距离-切比雪夫距离
人工智能·深度学习·算法·机器学习·自然语言处理·概率论
ulias2123 小时前
多态理论与实践
java·开发语言·前端·c++·算法