Map Sum Pairs — Trie with Cumulative Sum Propagation
Advertisement
Problem Statement
LeetCode 677 — Map Sum Pairs | Difficulty: Medium
Design a MapSum class that supports two operations:
MapSum()— initialise the structure.void insert(String key, int val)— insert thekey-valpair. Ifkeyalready existed, overwrite its value.int sum(String prefix)— return the sum of all values whose keys start withprefix.
Example:
MapSum mapSum = new MapSum();
mapSum.insert("apple", 3);
mapSum.sum("ap"); → 3
mapSum.insert("app", 2);
mapSum.sum("ap"); → 5 ("apple"=3 + "app"=2)
mapSum.insert("apple", 5); // overwrite
mapSum.sum("ap"); → 7 ("apple"=5 + "app"=2)Constraints:
1 <= key.length, prefix.length <= 501 <= val <= 1000- At most
50calls to insert and sum.
Why This Problem Matters
Map Sum Pairs is the canonical cumulative-trie design problem, asked at Amazon, Google, Bloomberg, and Akuna Capital. It is a sibling of LeetCode 2416 (Sum of Prefix Scores) but with values instead of counts and with mutability (overwrite semantics) layered on top. Mastering it earns you the design-trie pattern that powers production autocomplete services, weighted search suggestions, and prefix-aggregated metrics dashboards.
The "store delta and propagate" technique generalises far beyond tries. It appears in segment trees with lazy propagation, prefix sum arrays under updates, and incremental aggregation in databases. Recognising the shared pattern is exactly what senior interviewers probe.
The Core Insight
Two design moves combine:
- Augment each trie node with a
totalfield = sum of values for all keys passing through. Thensum(prefix)is a plain trie walk that returnsnode.totalat the prefix end. O(prefix length). - Handle overwrite via a delta — keep a side hashmap
vals[key] = previous value. Oninsert(key, val), computedelta = val - vals.get(key, 0), updatevals[key] = val, and adddelta(notval) to everytotalalong the trie path.
The delta trick avoids a second walk to subtract the old value. If the key is new, delta = val. If it is being overwritten, delta may be positive or negative — both work.
Why O(L) per operation? Insert touches L nodes (one per character). Sum touches at most L nodes (walks down to prefix tip). No subtree DFS needed.
Visual Dry Run
Operations:
insert("apple", 3) → vals = {"apple":3}, delta = 3
insert("app", 2) → vals = {"apple":3,"app":2}, delta = 2
insert("apple", 5) → vals = {"apple":5,"app":2}, delta = 5 - 3 = +2Trie state after all inserts:
root (total = 0)
|-- a (total = 7)
|-- p (total = 7)
|-- p (total = 7)
|-- l (total = 5)
|-- e (total = 5)Walk for sum("ap") → reach node 'p' (depth 2), return total = 7. Walk for sum("app") → reach inner 'p' (depth 3), return total = 7. Walk for sum("apple") → reach 'e' (depth 5), return total = 5.
The delta of +2 on the third insert flowed cleanly through nodes 'a', 'p', 'p', 'l', 'e' — exactly the nodes already on the path of "apple". Nodes outside the path were untouched.
Solution (Optimal) — Trie with Delta Propagation
Python
class TrieNode:
__slots__ = ("children", "total")
def __init__(self):
self.children = {}
self.total = 0
class MapSum:
def __init__(self):
self.root = TrieNode()
self.vals = {} # key -> previously inserted value (for delta)
def insert(self, key: str, val: int) -> None:
delta = val - self.vals.get(key, 0)
self.vals[key] = val
node = self.root
for ch in key:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.total += delta
def sum(self, prefix: str) -> int:
node = self.root
for ch in prefix:
if ch not in node.children:
return 0
node = node.children[ch]
return node.totalJavaScript
class TrieNode {
constructor() {
this.children = {};
this.total = 0;
}
}
class MapSum {
constructor() {
this.root = new TrieNode();
this.vals = new Map();
}
insert(key, val) {
const delta = val - (this.vals.get(key) ?? 0);
this.vals.set(key, val);
let node = this.root;
for (const ch of key) {
if (!node.children[ch]) node.children[ch] = new TrieNode();
node = node.children[ch];
node.total += delta;
}
}
sum(prefix) {
let node = this.root;
for (const ch of prefix) {
if (!node.children[ch]) return 0;
node = node.children[ch];
}
return node.total;
}
}Complexity
- Insert: O(L) where L = key length.
- Sum: O(P) where P = prefix length.
- Space: O(total characters across all keys).
Common Mistakes
- Adding
valinstead ofdeltaon overwrite — double-counts. The firstinsert("apple", 3)followed byinsert("apple", 5)would store 8, not 5. - Forgetting the side
valsmap — without remembering the previous value, you cannot compute delta correctly. - Storing values only at terminal nodes and DFS'ing on each sum — works but
sum()becomes O(subtree size), which fails the constraints if the trie is dense. - Initialising
totallazily without0— JavaScriptundefinedarithmetic producesNaN; always initialise. - Using a global counter and recomputing from scratch each insert — O(N times L) per insert and unnecessary.
- Treating the prefix walk as needing to terminate at a key end —
sum()walks to the last char of the prefix only, regardless of whether a key ends there.
Interview Tips
- Lead with the alternative: hashmap of all keys + linear scan per
sum. Acknowledge it works but is O(N times L) per query. - Pitch the trie augmentation: "Each node carries a running total of values along its subtree. A sum query is just a walk."
- Make the delta trick explicit: say "On overwrite, we add
(new - old)so we do not need a second walk." - Mention that the side
valsmap handles overwrite correctly with O(1) memory per key. - For follow-ups, mention that this generalises to "top-k by value with prefix" via heap or sorted set per node.
Follow-up Questions
- Delete a key?
insert(key, 0)works — propagates-oldalong the path. Optionally clean up empty nodes. - Top-k keys with prefix sorted by value? Per-node sorted structure or DFS the subtree, sort, take top-k.
- What if values can be negative? The algorithm is unchanged — deltas can be negative; totals can be negative.
- Range sum (sum over keys in
[lo, hi])? Trie does not support range elegantly; switch to a balanced BST or order-statistic tree. - Persistence (versioned MapSum)? Use immutable trie nodes with path copying — each insert creates O(L) new nodes.
Key Takeaways
- Map Sum Pairs uses a counted trie augmented with cumulative
totalat every node. - The delta trick
delta = new - oldhandles overwrite in a single walk. - Insert and sum are both O(L) — independent of total key count.
- Always pair the trie with a side
valsmap to remember previous values for deltas. - This template powers weighted autocomplete, prefix-aggregated metrics, and FAANG search ranking services.
- Recognising the "delta + lazy propagation" pattern unlocks segment trees, lazy DS, and many advanced trie variants.
Advertisement