Skip to content

Prefix Sum with HashMap

Sliding window fails the moment an array can contain negative numbers: growing the window no longer grows the sum, so “shrink while too big” stops making sense. It also fails when a problem asks you to count subarrays rather than find the best one.

The pattern that handles both is prefix sums plus a hash map. It rests on one identity:

sum(i..j)=prefix[j]prefix[i1]\text{sum}(i..j) = \text{prefix}[j] - \text{prefix}[i-1]

Rearranged, a subarray ending at j sums to k exactly when some earlier prefix equals prefix[j]k\text{prefix}[j] - k. So instead of searching for subarrays, you count prefix values you have already seen — one pass, one dict, O(n)O(n).

  • The prefix-sum identity, and the rearrangement that turns it into a lookup.
  • Why the map must be seeded with {0: 1}, and what breaks without it.
  • Counting occurrences vs. remembering the earliest index — the two flavours, and which each problem needs.
  • The remainder and balance transforms that make this pattern cover far more than plain sums.
  • Three real LeetCode problems solved in the browser: 560, 974, 525.

Two flavours. Which one you need depends on whether the answer is a count or a length.

prefix_sum_hashmap_template.py
# FLAVOUR 1 -- COUNT subarrays. Map: prefix value -> how many times seen.
def count_subarrays_with_sum(nums, k):
    seen = {0: 1}          # one empty prefix, so subarrays starting at index 0 count
    running = 0
    count = 0
    for x in nums:
        running += x
        count += seen.get(running - k, 0)      # every earlier match is a subarray
        seen[running] = seen.get(running, 0) + 1
    return count
 
 
# FLAVOUR 2 -- LONGEST subarray. Map: prefix value -> EARLIEST index seen.
def longest_subarray_with_sum(nums, k):
    first = {0: -1}        # empty prefix sits "before" index 0
    running = 0
    best = 0
    for i, x in enumerate(nums):
        running += x
        if running - k in first:
            best = max(best, i - first[running - k])
        if running not in first:               # only record the FIRST occurrence
            first[running] = i
    return best
 
 
print(count_subarrays_with_sum([1, 1, 1], 2))          # 2
print(longest_subarray_with_sum([1, -1, 5, -2, 3], 3))  # 4

Take nums = [3, 4, 7, 2, -3, 1, 4, 2], k = 7:

inums[i]runningrunning - kseen before?count
033-4no0
1470yes (seed)1
27147yes2
32169no2
4-3136no2
51147yes3
641811no3
722013yes4

Four subarrays: [3,4], [7], [7,2,-3,1], [1,4,2]. Notice the seed firing at i = 1 — that is the subarray starting at index 0, and it is exactly what a missing {0: 1} would lose.

TimeSpace
Brute force over all subarraysO(n2)O(n^2)O(1)O(1)
Prefix sums + hash mapO(n)O(n)O(n)O(n)

The chips under the row are the map from prefix sum to how many times it has been seen. Watch the sentinel {0: 1} do its work on the very first hit.

arrayA subarray sums to k exactly when two prefix sums differ by kLC 560 · O(n) time and space
102132-33141516
0×1
k3
setupSeed the map with {0: 1}. That sentinel represents the empty prefix and is what lets a subarray starting at index 0 be counted — forgetting it is the classic off-by-one here.
1/9

The array contains a negative number, which is precisely why a sliding window cannot solve this: shrinking from the left could increase the sum, so 'too big, shrink' is unsound. Prefix sums do not care about sign.

ApproachTimeSpace
Every subarray, summed from scratchO(n3)O(n^3)O(1)O(1)
Every subarray, running sumO(n2)O(n^2)O(1)O(1)
Prefix sum + hash mapO(n)O(n)O(n)O(n)

The O(n)O(n) space is unavoidable here and worth naming: the map can hold nn distinct prefix sums. That is the trade this pattern makes — memory for a factor of nn in time — and it is the answer to “can you do it in O(1)O(1) space?” (you cannot, in general).

The power of this pattern is that you can transform the array first, turning an unrelated-looking condition into “sum equals a target”.

VariantThe transformCanonical problem
Sum equals kNone560 Subarray Sum Equals K
Sum divisible by kKey on running % k instead of running974 · 523
Equal count of two symbolsMap one to +1, the other to -1; look for sum 0525 Contiguous Array
Exactly k odd numbersMap odd to 1, even to 0; look for sum k1248 Nice Subarrays
Count on a tree pathSame map, but undo the entry when leaving the node437 Path Sum III
2D submatrix sumsFix a row pair, collapse columns to 1D, apply the template1074

LC 560 — Subarray Sum Equals K · Medium

Section titled “LC 560 — Subarray Sum Equals K · Medium”

Problem. Given an integer array nums and an integer k, return the total number of contiguous subarrays whose sum equals k.

Constraints. 1 <= len(nums) <= 2 * 10^4, -1000 <= nums[i] <= 1000, -10^7 <= k <= 10^7. Note the negatives — this is why it is not a sliding window.

Examples. nums = [1,1,1], k = 2 gives 2 · nums = [1,2,3], k = 3 gives 2 · nums = [1,-1,0], k = 0 gives 3

Editorial — approach, complexity, follow-ups

A subarray ending at the current index sums to k exactly when some earlier prefix equals running - k. So the number of such subarrays is the number of times running - k has been seen — which is what the map stores.

Time O(n)O(n). Space O(n)O(n).

Note the answer counts subarrays, so equal prefix values must be tallied, not overwritten — [1,-1,0] reaches prefix 0 three times and each pairing is a distinct valid subarray.

Follow-ups you should expect: “Longest such subarray instead?” — switch to flavour 2, storing earliest indices. “All values positive?” — then a sliding window works and gives O(1)O(1) space; say so, it shows you know the trade. “Do it on a binary tree?” — LC 437: same map, but decrement the entry as you unwind so only the current root-to-node path is in scope.

LC 974 — Subarray Sums Divisible by K · Medium

Section titled “LC 974 — Subarray Sums Divisible by K · Medium”

Problem. Return the number of contiguous subarrays whose sum is divisible by k.

Constraints. 1 <= len(nums) <= 3 * 10^4, -10^4 <= nums[i] <= 10^4, 2 <= k <= 10^4.

Examples. nums = [4,5,0,-2,-3,1], k = 5 gives 7 · nums = [5], k = 9 gives 0 · nums = [-1,2,9], k = 2 gives 2

Editorial — approach, complexity, follow-ups

sum(i..j)\text{sum}(i..j) is divisible by k exactly when prefix[j]prefix[i1](modk)\text{prefix}[j] \equiv \text{prefix}[i-1] \pmod{k}. So instead of looking up running - k, look up running itself — among prefixes already reduced mod k.

Time O(n)O(n). Space O(k)O(k) — only k distinct remainders exist, which is tighter than the O(n)O(n) of LC 560.

Follow-ups you should expect: “Is there a subarray of length ≥ 2 whose sum is divisible by k?” — LC 523, same idea plus an index check for the length constraint. “Why is space O(k)O(k) not O(n)O(n)?” — because remainders live in [0, k), so a k-slot list beats a dict here.

Problem. Given a binary array nums, return the length of the longest contiguous subarray with an equal number of 0s and 1s.

Constraints. 1 <= len(nums) <= 10^5, nums[i] is 0 or 1.

Examples. nums = [0,1] gives 2 · nums = [0,1,0] gives 2 · nums = [0,1,1,1,1,1,0,0,0] gives 6 · nums = [1,1,1] gives 0

Editorial — approach, complexity, follow-ups

Map 0 -> -1 and 1 -> +1. A subarray has equal counts exactly when its mapped sum is 0, i.e. when two prefixes share the same running balance. The longest such subarray for a given balance spans from its earliest occurrence to the current index.

Time O(n)O(n). Space O(n)O(n).

[1,1,1] returning 0 is the “no valid subarray” case — the balance never repeats, so best stays at its initial 0.

Follow-ups you should expect: “Equal counts of three symbols?” — key the map on a tuple of differences, e.g. (count_a - count_b, count_b - count_c). “Return the subarray itself?” — also store the index where best was set. “Equal counts of any two values in a non-binary array?” — same idea, one map per pair of interest.

Generated from the problem database, so each entry carries its sheet membership and reported companies. Tick them off as you go — progress is saved in this browser, and the Export button writes it to a file you can keep.

8 problems
0 easy7 medium1 hard

Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.

LC 560 with k = 3, nums = [1, 2, 3, -3, 1, 1, 1]. The want column is the whole idea: a subarray ending here sums to k exactly when some earlier prefix equals running − k.

ivrunningwant = running − kseen countcountmap after
00{0: 1}
011−200{0:1, 1:1}
123011{0:1, 1:1, 3:1}
236312{0:1, 1:1, 3:1, 6:1}
3−33013{0:1, 1:1, 3:2, 6:1}
414114{0:1, 1:1, 3:2, 4:1, 6:1}
5152045:1
6163266:2

Answer: 6 subarrays.

Three things that table makes concrete:

  • The {0: 1} sentinel earns its keep at i = 1. The subarray [1, 2] starts at index 0, so the “earlier prefix” it needs is the empty prefix. Omit the sentinel and every subarray starting at index 0 is missed — the single most common bug in this pattern.
  • At i = 3 the running sum returns to 3, a value already seen. That is only possible because of the negative number, and it is exactly why the map counts occurrences rather than storing a boolean.
  • At i = 6, want = 3 has been seen twice, so the count jumps by 2. Using a set instead of a counter would add 1 and silently undercount.
They askWhat they’re checkingThe answer
“Why not a sliding window?”Whether you know each pattern’s limitsNegative values break the window’s monotonic sum; and windows find extremes, not counts
“Why seed with {0: 1}?”Whether you derived it or copied itIt represents the empty prefix, so subarrays starting at index 0 are counted
“Count vs. earliest index?”Whether you match the map to the questionCounting a total means tally every occurrence; measuring a length means keep only the first
“What if all values are positive?”Trade-off awarenessA window then works and needs O(1)O(1) space instead of O(n)O(n)
“Space is O(n)O(n) — can you do better?”PrecisionFor the divisibility variant, yes: O(k)O(k), since only k remainders exist
“Extend to 2D”CompositionFix a pair of rows, collapse each column into a running sum, and run the 1D template — O(rows2cols)O(rows^2 \cdot cols)
  • k = 0 — forces the count-before-record ordering; [1,-1,0] is the test.
  • Negative numbers — the entire reason for this pattern; make sure nothing in your solution assumes monotonicity.
  • Zeros in the array — create repeated prefix values and therefore extra valid subarrays; they must be tallied, not deduplicated.
  • Whole array is the answer — verifies the seed entry works.
  • No valid subarray — must return 0, not -1 or float("inf").
  • Single element[5], k = 5 gives 1; [5], k = 9 gives 0.
  • Overflow — irrelevant in Python (arbitrary-precision ints), but worth naming if the interviewer thinks in C++ or Java.
  • Earliest-index guard (length variants) — never overwrite a balance already in the map.
pch.quizTag Prefix sum with a hash map — self-check
  1. Why is the map seeded with {0: 1}?

    pch.quizShowAnswer

    B — It represents the empty prefix, which is what a subarray starting at index 0 needs to match against — A subarray from index 0 to i has sum running[i] − running[-1], and running[-1] is the empty prefix, 0. Without the sentinel every such subarray is missed — and the bug is invisible on inputs whose answers all start later.

  2. The array contains negative numbers. Why does that rule out a sliding window?

    pch.quizShowAnswer

    B — Shrinking from the left could increase the sum, so the 'too big → shrink' decision is no longer sound — The window pattern needs the sum to move monotonically as the window grows and shrinks. A negative at the left edge breaks that, and the algorithm silently returns wrong answers. Prefix sums are sign-agnostic, which is exactly why they are the fallback.

  3. Why does the map count occurrences instead of storing a set of seen sums?

    pch.quizShowAnswer

    B — The same prefix sum can occur several times, and each occurrence is a distinct valid subarray to count — With negatives the running sum can revisit a value. If prefix p occurred three times, there are three distinct subarrays ending here that sum to k. A set would add 1 and undercount.

  4. Does the lookup happen before or after inserting the current prefix sum, and why?

    pch.quizShowAnswer

    B — Before — otherwise with k = 0 the current prefix matches itself and counts a phantom empty subarray — The k = 0 case is what exposes it: running − 0 == running, so inserting first makes every index count itself. Lookup, then insert.

  5. Same problem but you need the LONGEST subarray summing to k, not the count. What changes?

    pch.quizShowAnswer

    B — Store the FIRST index at which each prefix sum occurred, and do not overwrite it — For a longest span you want the earliest possible start, so the first occurrence is the useful one — overwriting on a repeat would shorten the answer. That is LC 325, and the not-overwriting detail is the entire difficulty.

  • Cue — count or measure contiguous subarrays with a target sum, and negatives are possible (which rules out a sliding window).
  • Key identitysum(i..j) == running[j] − running[i-1]. So a subarray ending at j sums to k exactly when some earlier prefix equals running[j] − k.
  • Templateseen = {0: 1}; per element: running += v; count += seen.get(running - k, 0); then seen[running] += 1.
  • ComplexityO(n)O(n) time, O(n)O(n) space. The space is unavoidable.
  • Remember — the {0: 1} sentinel; count occurrences, do not use a set; look up before inserting.
  • Variants — longest instead of count (store the first index); divisible by k (key on running % k); equal 0s and 1s (map 0 → −1 and look for sum 0).
  • sum(i..j)=prefix[j]prefix[i1]\text{sum}(i..j) = \text{prefix}[j] - \text{prefix}[i-1], rearranged into a lookup: a subarray ending here hits the target when running - k has been seen before.
  • Two flavours: count occurrences ({0: 1}) for “how many”, earliest index ({0: -1}) for “how long”.
  • Seed the map, and for length variants never overwrite an existing key.
  • Count before recording so k = 0 cannot match a zero-length subarray.
  • Transform first, then apply the template: % k for divisibility, +1/-1 for equal counts, odd/even for parity counts. That is what makes one small template cover a dozen problems.
  • Choose this over a window whenever values can be negative, or the question asks for a count.

Next: Kadane and Maximum Subarray — the other classic answer to “contiguous subarray with negatives”, when you want the best sum rather than a count.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading