- 实现一个 MapSum 类里的两个方法,insert 和 sum。
-
- 对于方法 insert,你将得到一对(字符串,整数)的键值对。字符串表示键,整数表示值。如果键已经存在,那么原来的键值对将被替代成新的键值对。
-
- 对于方法 sum,你将得到一个表示前缀的字符串,你需要返回所有以该前缀开头的键的值的总和。
-
- 示例 1:
-
- 输入: insert("apple", 3), 输出: Null
- 输入: sum("ap"), 输出: 3
- 输入: insert("app", 2), 输出: Null
- 输入: sum("ap"), 输出: 5
-
- 来源:力扣(LeetCode)
- 链接:https://leetcode-cn.com/problems/map-sum-pairs
- 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
| 时间 | 空间 | |
|---|---|---|
| insert | O(1) (不考虑哈希冲突的话) | O(n) |
| sum | O(n∗len(prefix)) | O(n) |
| 备注 | n 是 hashmap 的 key 总数,prefix 是要查找的前缀 | n 是字符串数量 |
Python Code
class MapSum(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.hashmap = {}
def insert(self, key, val):
"""
:type key: str
:type val: int
:rtype: None
"""
self.hashmap[key] = val
def sum(self, prefix):
"""
:type prefix: str
:rtype: int
"""
res = 0
for key in self.hashmap:
if key.startswith(prefix):
res += self.hashmap[key]
return res
# Your MapSum object will be instantiated and called as such:
# obj = MapSum()
# obj.insert(key,val)
# param_2 = obj.sum(prefix)
sum 操作的时候,先判断 prefix 是否存在前缀树中,然后找到 prefix 最后的节点,开始 DFS。DFS
大问题:dfs(node) 应该要返回以这个节点开始的所有路径中的 value 的和。
小问题:dfs(node.child),先找到以 node 的子节点开始的所有路径中的 value 的和。不过由于 node 有很多子节点,所以我们需要一个循环。
大问题与小问题的关系:当我们求出了全部 dfs(node.child),那么
dfs(node) = node.value + dfs(node.child1) + dfs(node.child2) + ...递归出口:如果节点不存在,我们直接返回 0,停止递归。
insert 操作的时间复杂度是 O(len(key)),sum 操作的时间复杂度是 O(mn),m 是字符集中字符数量,n 是字符串长度。| 时间 | 空间 | |
|---|---|---|
| insert | O(len(key)) | O(mn) |
| sum | O(mn) | O(mn) |
| 备注 | m 是字符集中字符数量,n 是字符串长度 |
TypeScript Code
class TrieNode {
value: number;
children: Array;
constructor(value: number) {
this.value = value;
this.children = Array(26);
}
}
class MapSum {
private root: TrieNode;
constructor() {
this.root = this._getTrieNode(0);
}
private _getTrieNode(value: number): TrieNode {
return new TrieNode(value);
}
private _char2Index(char: string): number {
return char.toLowerCase().charCodeAt(0) - 97;
}
insert(key: string, val: number): void {
let crawl: TrieNode = this.root;
for (let char of key) {
const index: number = this._char2Index(char);
if (!crawl.children[index]) {
crawl.children[index] = this._getTrieNode(0);
}
crawl = crawl.children[index];
}
crawl.value = val;
}
private _dfs(crawl: TrieNode): number {
if (!crawl) return 0;
return crawl.children.reduce(
(res: number, pointer: TrieNode): number => {
return res + (pointer ? this._dfs(pointer) : 0);
},
crawl.value,
);
}
sum(prefix: string): number {
let crawl: TrieNode = this.root;
for (let char of prefix) {
const index: number = this._char2Index(char);
if (!crawl.children[index]) return 0;
crawl = crawl.children[index];
}
return this._dfs(crawl);
}
}
/**
* Your MapSum object will be instantiated and called as such:
* var obj = new MapSum()
* obj.insert(key,val)
* var param_2 = obj.sum(prefix)
*/
在每个节点上存 prefixSum,可以把 sum 操作的时间复杂度降到 O(1),代价是在 insert 的时候需要先检查 key 是否已经存在,存在的话要先从 prefixSum 中减去旧的 value,再加上新的 value。
| 时间 | 空间 | |
|---|---|---|
| insert | O(len(key)) | O(mn) |
| sum | O(1) | O(mn) |
| 备注 | m 是字符集中字符数量,n 是字符串长度 |
class TrieNode {
value: number;
prefixSum: number;
children: Array;
constructor(value: number) {
this.value = value;
this.prefixSum = 0;
this.children = Array(26);
}
}
class MapSum {
private root: TrieNode;
constructor() {
this.root = this._getTrieNode(0);
}
private _getTrieNode(value: number): TrieNode {
return new TrieNode(value);
}
private _char2Index(char: string): number {
return char.toLowerCase().charCodeAt(0) - 97;
}
search(key: string): number {
let crawl: TrieNode = this.root;
for (let char of key) {
const index: number = this._char2Index(char);
if (!crawl.children[index]) return 0;
crawl = crawl.children[index];
}
return crawl.value;
}
insert(key: string, val: number): void {
let crawl: TrieNode = this.root;
const existedVal: number = this.search(key);
for (let char of key) {
const index: number = this._char2Index(char);
if (!crawl.children[index]) {
crawl.children[index] = this._getTrieNode(0);
}
crawl = crawl.children[index];
crawl.prefixSum = crawl.prefixSum - existedVal + val;
}
crawl.value = val;
}
sum(prefix: string): number {
let crawl: TrieNode = this.root;
for (let char of prefix) {
const index: number = this._char2Index(char);
if (!crawl.children[index]) return 0;
crawl = crawl.children[index];
}
return crawl.prefixSum;
}
}
/**
* Your MapSum object will be instantiated and called as such:
* var obj = new MapSum()
* obj.insert(key,val)
* var param_2 = obj.sum(prefix)
*/
在方法 3 的基础上再加一个 hashmap,把以某个前缀开始的 key 和相应的 value存起来,比如,
(dark, 3),然后再插入一个 (darkest, 2){ dark: 3, darkest: 2 }(darkest, 5),覆盖了之前的值,那 hashmap 就变成了:
{ dark: 3, darkest: 5 }| 时间 | 空间 | |
|---|---|---|
| insert | O(len(key)) | O(mn) |
| sum | O(len(prefix)) | O(mn) |
| 备注 | m 是字符集中字符数量,n 是字符串长度 |
class TrieNode {
value: number;
prefixSum: number;
children: Array;
constructor(value: number) {
this.value = value;
this.prefixSum = 0;
this.children = Array(26);
}
}
class MapSum {
private root: TrieNode;
private existedWords: {
[key: string]: number;
};
constructor() {
this.root = this._getTrieNode(0);
this.existedWords = {};
}
private _getTrieNode(value: number): TrieNode {
return new TrieNode(value);
}
private _char2Index(char: string): number {
return char.toLowerCase().charCodeAt(0) - 97;
}
insert(key: string, val: number): void {
let crawl: TrieNode = this.root;
for (let char of key) {
const index: number = this._char2Index(char);
if (!crawl.children[index]) {
crawl.children[index] = this._getTrieNode(0);
}
crawl = crawl.children[index];
if (key in this.existedWords) {
crawl.prefixSum -= this.existedWords[key];
}
crawl.prefixSum += val;
}
this.existedWords[key] = val;
crawl.value = val;
}
sum(prefix: string): number {
let crawl: TrieNode = this.root;
for (let char of prefix) {
const index: number = this._char2Index(char);
if (!crawl.children[index]) return 0;
crawl = crawl.children[index];
}
return crawl.prefixSum;
}
}
/**
* Your MapSum object will be instantiated and called as such:
* var obj = new MapSum()
* obj.insert(key,val)
* var param_2 = obj.sum(prefix)
*/