键值映射

标签: 设计 字典树 哈希表 字符串

难度: Medium

设计一个 map ,满足以下几点:

  • 字符串表示键,整数表示值
  • 返回具有前缀等于给定字符串的键的值的总和

实现一个 MapSum 类:

  • MapSum() 初始化 MapSum 对象
  • void insert(String key, int val) 插入 key-val 键值对,字符串表示键 key ,整数表示值 val 。如果键 key 已经存在,那么原来的键值对 key-value 将被替代成新的键值对。
  • int sum(string prefix) 返回所有以该前缀 prefix 开头的键 key 的值的总和。

示例 1:

输入:
["MapSum", "insert", "sum", "insert", "sum"]
[[], ["apple", 3], ["ap"], ["app", 2], ["ap"]]
输出:
[null, null, 3, null, 5]

解释:
MapSum mapSum = new MapSum();
mapSum.insert("apple", 3);  
mapSum.sum("ap");           // 返回 3 (apple = 3)
mapSum.insert("app", 2);    
mapSum.sum("ap");           // 返回 5 (apple + app = 3 + 2 = 5)

提示:

  • 1 <= key.length, prefix.length <= 50
  • keyprefix 仅由小写英文字母组成
  • 1 <= val <= 1000
  • 最多调用 50insertsum

Submission

运行时间: 24 ms

内存: 0.0 MB

class TrieNode(object):
    __slots__ = 'children', 'score'
    def __init__(self):
        self.children = {}
        self.score = 0

class MapSum(object):
    def __init__(self):
        self.map = {}
        self.root = TrieNode()

    def insert(self, key, val):
        delta = val - self.map.get(key, 0)
        self.map[key] = val
        cur = self.root
        cur.score += delta
        for char in key:
            cur = cur.children.setdefault(char, TrieNode())
            cur.score += delta

    def sum(self, prefix):
        cur = self.root
        for char in prefix:
            if char not in cur.children:
                return 0
            cur = cur.children[char]
        return cur.score

Explain

该题解采用了字典和字典树(Trie)的结合方法。主要包括两个部分:1. 一个普通字典 'map' 存储键值对,用于记录每个键对应的最新值。2. 字典树 'root',其中每个节点存储了到该节点为止的所有键的值的累积和。插入操作时,首先计算当前键值与原始值的差值(delta),然后更新字典树中经过该键的所有节点的累积值。查询前缀和时,遍历字典树直到前缀的末尾,然后返回该节点的累积和。

时间复杂度: O(k) for insert, O(p) for sum

空间复杂度: O(N*k)

class TrieNode(object):
    __slots__ = 'children', 'score'
    def __init__(self):
        self.children = {}  # 子节点字典
        self.score = 0     # 到当前节点的值的累积和

class MapSum(object):
    def __init__(self):
        self.map = {}       # 存储键值对
        self.root = TrieNode()  # 字典树的根节点

    def insert(self, key, val):
        delta = val - self.map.get(key, 0)  # 计算值的变化量
        self.map[key] = val                 # 更新或添加键值对
        cur = self.root
        cur.score += delta                  # 更新根节点的累积值
        for char in key:
            cur = cur.children.setdefault(char, TrieNode())
            cur.score += delta            # 更新经过的每个节点的累积值

    def sum(self, prefix):
        cur = self.root
        for char in prefix:
            if char not in cur.children:
                return 0                   # 前缀不存在时返回0
            cur = cur.children[char]
        return cur.score                   # 返回前缀对应的累积值

Explore

在插入操作中计算当前键值与原始值的差值(delta)是为了正确维护字典树中各节点的累积值。如果直接使用新的键值更新,那么我们将无法区分出键值的增加或减少的部分,导致无法正确调整已存在键的累积和。使用差值(delta)可以确保无论是增加还是减少键值,都能通过加上delta来正确更新所有经过该键的节点的累积和。

当插入操作中的键已存在于字典中时,首先计算出新值与旧值的差值delta。然后从字典树的根节点开始,遍历到表示该键的每一个字符对应的节点,逐一将delta加到这些节点的累积值中。这样,字典树中经过该键的所有节点的累积值都会被正确地调整,以反映新的键值。

在查询前缀和的操作中,如果前缀不存在,直接返回0是一个合理的处理方式,因为这意味着没有任何键是以该前缀开始的,因此前缀和自然应该是0。这种处理方式不会影响其他操作或数据的准确性,因为它仅仅反映了前缀和查询的结果,并不改变任何存储或累积值。

字典树的根节点在每次插入操作时都更新其累积值,这样设计的主要优点是方便快速地获取所有键的总累积和。根节点的累积值代表了所有存储在字典树中的键的值的总和,这使得任何时候对整体键值的累积查询变得非常高效。此外,更新根节点的累积值也是维护整个字典树数据一致性的关键步骤。