Medium
题目描述
设计一个映射,允许你执行以下操作:
- 将字符串键映射到给定值。
- 返回具有等于给定字符串的前缀的键的值的总和。
实现 MapSum 类:
MapSum()初始化MapSum对象。void insert(String key, int val)向映射中插入key-val键值对。如果键已经存在,原来的键值对将被新的键值对覆盖。int sum(string prefix)返回所有以prefix为前缀的键对应的值的总和。
示例 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 <= 50key和prefix仅由小写英文字母组成。1 <= val <= 1000- 最多调用 50 次
insert和sum。
解题思路
这道题有两种常见的解法:
方法一:哈希表 + 暴力搜索
最直接的思路是使用哈希表存储所有的键值对,在 sum 操作时遍历所有键,找出以给定前缀开头的键并累加它们的值。这种方法实现简单,但在前缀匹配时需要遍历所有键,时间复杂度较高。
方法二:字典树(Trie)(推荐) 更高效的解法是使用字典树。我们可以构建一个字典树,每个节点除了存储字符信息外,还存储从根节点到当前节点路径上所有完整单词的值的总和。这样在查询前缀和时,只需要走到前缀对应的节点,直接返回该节点存储的总和即可。
具体实现:
- 每个 Trie 节点包含26个子节点(对应a-z)和一个
sum值 insert时,沿着路径更新每个节点的sum值- 如果是更新操作(key已存在),需要先减去旧值再加上新值
sum时,沿着前缀路径找到对应节点,返回其sum值
字典树方法的优势在于 sum 操作的时间复杂度只依赖于前缀长度,而不是存储的键的数量。
代码实现
class MapSum {
private:
struct TrieNode {
TrieNode* children[26];
int sum;
TrieNode() : sum(0) {
for (int i = 0; i < 26; i++) {
children[i] = nullptr;
}
}
};
TrieNode* root;
unordered_map<string, int> keyToVal;
public:
MapSum() {
root = new TrieNode();
}
void insert(string key, int val) {
int oldVal = keyToVal.count(key) ? keyToVal[key] : 0;
keyToVal[key] = val;
int delta = val - oldVal;
TrieNode* node = root;
for (char c : key) {
int idx = c - 'a';
if (!node->children[idx]) {
node->children[idx] = new TrieNode();
}
node = node->children[idx];
node->sum += delta;
}
}
int sum(string prefix) {
TrieNode* node = root;
for (char c : prefix) {
int idx = c - 'a';
if (!node->children[idx]) {
return 0;
}
node = node->children[idx];
}
return node->sum;
}
};
class MapSum:
def __init__(self):
self.trie = {}
self.key_to_val = {}
def insert(self, key: str, val: int) -> None:
old_val = self.key_to_val.get(key, 0)
self.key_to_val[key] = val
delta = val - old_val
node = self.trie
for c in key:
if c not in node:
node[c] = {'sum': 0}
node = node[c]
node['sum'] += delta
def sum(self, prefix: str) -> int:
node = self.trie
for c in prefix:
if c not in node:
return 0
node = node[c]
return node['sum']
public class MapSum {
private class TrieNode {
public TrieNode[] Children;
public int Sum;
public TrieNode() {
Children = new TrieNode[26];
Sum = 0;
}
}
private TrieNode root;
private Dictionary<string, int> keyToVal;
public MapSum() {
root = new TrieNode();
keyToVal = new Dictionary<string, int>();
}
public void Insert(string key, int val) {
int oldVal = keyToVal.ContainsKey(key) ? keyToVal[key] : 0;
keyToVal[key] = val;
int delta = val - oldVal;
TrieNode node = root;
foreach (char c in key) {
int idx = c - 'a';
if (node.Children[idx] == null) {
node.Children[idx] = new TrieNode();
}
node = node.Children[idx];
node.Sum += delta;
}
}
public int Sum(string prefix) {
TrieNode node = root;
foreach (char c in prefix) {
int idx = c - 'a';
if (node.Children[idx] == null) {
return 0;
}
node = node.Children[idx];
}
return node.Sum;
}
}
var MapSum = function() {
this.trie = {};
this.keyToVal = new Map();
};
MapSum.prototype.insert = function(key, val) {
const oldVal = this.keyToVal.get(key) || 0;
this.keyToVal.set(key, val);
const delta = val - oldVal;
let node = this.trie;
for (let c of key) {
if (!(c in node)) {
node[c] = {sum: 0};
}
node = node[c];
node.sum += delta;
}
};
MapSum.prototype.sum = function(prefix) {
let node = this.trie;
for (let c of prefix) {
if (!(c in node)) {
return 0;
}
node = node[c];
}
return node.sum;
};
复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| insert | O(m) | O(m) |
| sum | O(p) | O(1) |
其中:
- m 是键的长度
- p 是前缀的长度
- 总空间复杂度:O(ALPHABET_SIZE × N × M),其中 N 是插入的键的数量,M 是键的平均长度
相关题目
. Sort the Jumbled Numbers (Medium)