If every new value makes you scan all earlier ones, ask: can I store the information I'll need to query later? The important part isn't memorizing dict; it's choosing keys and values.

1. Decide what to store

Future question Key Value / container
Have I seen this? Value A set is enough
Where was it last seen? Value / character Index
How many occurrences? Value / character Count
Which words belong together? Canonical signature List of words
How many earlier prefixes match this one? Prefix sum Count

Python keys must be hashable. Integers, strings, and tuples of hashable values work; lists don't. For anagrams, a sorted string is a possible signature. Sorting a word of length m costs O(m log m): include key-construction time. Hash-table background

2. Two Sum: query before inserting

For [4, 1, 7] with target 8, reaching 7 means looking for 1. The map says 1 appeared at index 1; return [1, 2].

def two_sum(values, target):
    seen = {}
    for index, value in enumerate(values):
        complement = target - value
        if complement in seen:
            return [seen[complement], index]
        seen[value] = index
    return None

Invariant: the map contains only earlier indices at lookup time. [3] cannot reuse itself for target 6, while [3, 3] can form a pair. Average O(n) time, O(n) space. It avoids sorting and preserves original indices, at the cost of a map.

3. Common dict operations

counts = {"pear": 2}
exists = "pear" in counts
missing_count = counts.get("apple", 0)
counts["pear"] = counts.get("pear", 0) + 1
removed = counts.pop("apple", 0)
pairs = list(counts.items())

exists == True, missing_count == 0, removed == 0, and pairs == [('pear', 3)]. get doesn't insert a missing key; indexing a defaultdict can create its default. setdefault(key, []) inserts only when absent. keys(), values(), and items() return views, not independent snapshots. Python mapping API

Don't test existence with if seen.get(value): a stored index of 0 is falsey. Use value in seen. Don't change a dictionary's size while iterating over it.

4. Prefix sums: subtract boundaries instead of summing intervals

Define prefix[0] = 0 and prefix[right] as the sum of the first right elements. The half-open interval [left, right) sums to prefix[right] - prefix[left].

To count subarrays summing to target, query how often prefix - target occurred earlier.

def count_subarrays(values, target):
    frequencies = {0: 1}
    prefix = 0
    total = 0
    for value in values:
        prefix += value
        total += frequencies.get(prefix - target, 0)
        frequencies[prefix] = frequencies.get(prefix, 0) + 1
    return total
Read [1, -1, 1], target = 1 Current prefix Earlier prefix needed New answers
1 1 0 1
−1 0 −1 0
1 1 0 2
Step by step Prefix sums: look up old prefixes, then store

This figure needs JavaScript.

Total: 3, the two individual 1s and the entire array. Store counts, not just an index or a set. {0: 1} represents the empty prefix before the input. Query before inserting to avoid counting an empty subarray when target is zero.

Average O(n) time, O(n) space. It handles negative values without relying on sums growing with window size. Prefix sums suit static range queries; updates to the original array invalidate later prefixes.

5. Practice and transfer

What breaks if frequencies becomes a set?

Equal prefix sums at different positions represent different starts. A set loses multiplicity. [0, 0] with target 0 has 3 subarrays, not 2.

If Two Sum's input is sorted and we only need values, do we still need a map?

No. Two pointers take O(n) time and O(1) auxiliary space. Sorting first would cost O(n log n), and original indices are lost unless retained separately.