The problem
Design a MapSum class:
insert(key, val)storeskey → val. If the key already exists, the new value replaces the old one.sum(prefix)returns the total of the values of every key that starts withprefix.
insert("apple", 3)
sum("ap") -> 3
insert("app", 2)
sum("ap") -> 5After the second insert, both apple (3) and app (2) start with ap, so the sum is 5.
How to think about it
Start with brute force. Keep a plain dict of keys to values. For sum(prefix), loop over every key and add the value if key.startswith(prefix). That is O(N·L) per sum for N keys. It is simple and correct, and it already handles overwrites, because a dict keeps one value per key.
The key insight: a trie finds the prefix, and a value map handles overwrites. The trie answers “does any key start with this prefix?” in O(L): if the walk falls off, the answer is 0 straight away. The separate vals dict keeps exactly one value per key, so re-inserting a key never double counts. The solution below then totals the matching keys from vals, which keeps the code short and obviously correct.
The fully optimised version. To make sum O(L) as well, store a running total at every trie node. On insert, compute delta = val - old_val and add delta to each node on the key’s path. Then sum(prefix) is just the total stored at the prefix’s node. Mention this in an interview; it is the reason a trie fits this problem.
Which pattern is this? Trie with data on the nodes. The tell is “aggregate over all keys sharing a prefix”.
Approach
- Keep
vals = {}(key to latest value) and a trieroot = {}. insert(key, val): setvals[key] = val(this overwrites cleanly), then walk the key into the trie, creating nodes.sum(prefix): walk the prefix down the trie. If a character is missing, return 0.- Otherwise, add up
vals[k]for every keykthat starts withprefix.
Solution
class MapSum:
def __init__(self): self.vals = {}; self.root = {}
def insert(self, key, val):
self.vals[key] = val; n = self.root # overwrite, never add twice
for c in key: n = n.setdefault(c, {})
def sum(self, prefix):
n = self.root
for c in prefix:
if c not in n: return 0 # no key has this prefix
n = n[c]
return sum(v for k, v in self.vals.items() if k.startswith(prefix))
# insert("apple",3); sum("ap") -> 3; insert("app",2); sum("ap") -> 5Dry run
Running the example, plus an overwrite and two extra queries:
| Operation | vals after |
Prefix walk | Result |
|---|---|---|---|
insert("apple", 3) |
{apple: 3} |
— | — |
sum("ap") |
same | a→p found | 3 |
insert("app", 2) |
{apple: 3, app: 2} |
— | — |
sum("ap") |
same | a→p found | 5 |
insert("apple", 5) |
{apple: 5, app: 2} |
— | — |
sum("ap") |
same | a→p found | 7 |
sum("b") |
same | b missing |
0 |
The overwrite changes apple from 3 to 5, so the sum becomes 7, not 10.
Complexity
- Time:
insertis O(L). One dict write plus one walk of the key.sumis O(L + N·L) as written: the walk, then a scan of all N keys. With per-node totals,sumdrops to O(L). - Space: O(S) for the trie and the value map, where S is the total length of all keys.
Edge cases and pitfalls
- Overwrites. Adding
valto every node on insert, without subtracting the old value, double counts a re-inserted key. Use the delta. - Prefix with no keys returns 0 from the walk.
- Prefix equal to a whole key (
sum("apple")) includes that key itself. - Empty prefix sums every value.
Related problems
- Implement Trie (Prefix Tree) (LeetCode 208): the plain trie this extends.
- Search Suggestions System (LeetCode 1268): another “everything under this prefix” query.
- Design HashMap (LeetCode 706): the key-value side of this design.
- 1.Longest Common Prefix (LeetCode 14)
- 2.Implement Trie (Prefix Tree) (LeetCode 208)
- 3.Design Add and Search Words Data Structure (LeetCode 211)
- 4.Replace Words (LeetCode 648)
- 5.Map Sum Pairs (LeetCode 677)
- 6.Longest Word in Dictionary (LeetCode 720)
- 7.Search Suggestions System (LeetCode 1268)
- 8.Maximum XOR of Two Numbers in an Array (LeetCode 421)
- 9.Short Encoding of Words (LeetCode 820)
- 10.Word Search II (LeetCode 212)