携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情
题目描述
实现一个 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)
本题同 LeetCode677. 键值映射 是一样的。
解题思路——hashmap
遇事不决,hashmap。
这道题要求我们对于给定的前缀去计算属于该前缀的总和。那么我们怎么知道有哪些单词有这些前缀呢?
javascript 提供了一个 startsWith 方法,用于匹配给定的字符串是否是由另一个字符串开始。那么有了这个方法后,我们将所有的单词都存入 Map 集合中,后续调用 sum 方法时,只要 逐个 判断是否以给定的 prefix 打头,然后进行累加即可。
更多关于 startsWith 的用法,见MDN:String.prototype.startsWith() | MDN (mozilla.org)
题解
var MapSum = function() {
this.mp = new Map();
};
MapSum.prototype.insert = function(key, val) {
this.mp.set(key, val);
};
MapSum.prototype.sum = function(prefix) {
let sum = 0;
for(const [k,v] of this.mp) {
k.startsWith(prefix) && (sum+=v);
}
return sum;
};
解题思路—— Trie 树
老生常谈了,看到这种什么 insert 的,单词啊,前缀的,就要想到字典树了。
老观众应该都知道啥是字典树了,不懂的看看这篇文章:
水电费费
Trie 树的结构
这道题中我们除了往字典树中插入单词外,还需要有一个属性来保存 val 值,方便后续计算给定 prefix 的 sum 时使用。所以字典树的结构就出来了:
class Trie {
constructor() {
this.data = {};
this.val = 0;
}
}
Trie 树的 insert 方法
有了 Trie 树的结构后,我们来思考一下 insert 的逻辑。
插入的时候,除了单词以外,还需要保存 val 值。这道题我们最后会使用 DFS 进行 sum 值计算,也就是说,我们会深度遍历所有以 prefix 为前缀的单词(必须是结束单词),那么这里的 val 值我们就存在单词的结束位置,和结束标记一个层级。
insert(word, val) {
let r = this;
for (const w of word) {
if (r.data[w] === void 0) {
r.data[w] = new Trie();
}
r = r.data[w];
}
r.val = val; // 同结束标记一个层级
r['#'] = true;
}
Trie 树的 sum 方法
这道题有个需要注意的地方是,后续如果有重复单词插入的话,val 值是需要更新的。这边我用 DFS 统计以 prefix 开头的所有 val 值。
我们先将指针移动到 prefix 这个层级。然后对于该层开始的每个 Trie 节点,都累加 val 值,最后输出。
题解
这个 DFS 写的丑陋了点,后续看看能不能优化一下了。
class Trie {
constructor() {
this.data = {};
this.val = 0;
}
insert(word, val) {
let r = this;
for (const w of word) {
if (r.data[w] === void 0) {
r.data[w] = new Trie();
}
r = r.data[w];
}
r.val = val;
r['#'] = true;
}
sum(prefix) {
let r = this;
for (const w of prefix) {
if(!r) return 0;
r = r.data[w];
}
let sum = 0;
const dfs = (node) => {
if (!node) {
return;
}
sum += node.val;
for (const k in node.data) {
dfs(node.data[k])
}
}
dfs(r);
return sum;
}
}
var MapSum = function () {
this.trie = new Trie();
};
MapSum.prototype.insert = function (key, val) {
this.trie.insert(key, val);
};
MapSum.prototype.sum = function (prefix) {
return this.trie.sum(prefix);
};