#560MediumPrefix sum + hashmap
Subarray Sum Equals K
Start dumb: sum every subarray
Count the subarrays summing to k? Try them all: every start i, every
end j, keep a running sum as j grows. That’s O(n²) subarray
checks — workable for toy inputs, hopeless at LeetCode’s 2·10⁴ elements.
Find the bottleneck
Your first instinct might be a sliding window — but nums can hold
negatives and zeros, so growing the window doesn’t reliably raise the
sum and shrinking doesn’t reliably lower it. The window’s control signal
is dead. What the brute force wastes is different: it recomputes sums over
the same elements again and again, when every one of those sums is just a
difference of two running totals.
The optimal shape
One pass, one map:
- Seed the map with
{0 → 1}— the empty prefix. - Absorb each element into the running
prefix. - Look up
prefix − k; every hit is a subarray ending here. Add the count. - Then record the current
prefixin the map for future indices.
Each element is absorbed once with O(1) map work — O(n) time, O(n) space for the map. In the player below, the map in the state pane grows as the answer assembles; when a lookup hits, the matched subarray lights up in the array.
Watch it think
Step through the default, then stress the parts windows can’t handle:
zeros (0,0 | 0 — count the three), cancelling negatives (-1,1 | 0),
and the classic mixed case 3,4,7,2,-3,1,4,2 | 7.
Re-traces as you type — comma-separated integers (1-14 of them, each -99..99), a pipe, then the target k — e.g. "1,1,1 | 2" or "1,-1,1,-1 | 0". Capped at 32 characters so the tiles stay readable.
Step 1 of 15: Before anything is scanned the running sum is already 0, so that empty prefix is recorded once — skip this seed and every subarray starting at index 0 goes uncounted. Variables: i = —, prefix = 0, need = —, count = 0, k = 2, counts = {0→1}.
Data
Code
1fun subarraySum(nums: IntArray, k: Int): Int {2 val prefixCounts = HashMap<Int, Int>()3 prefixCounts[0] = 1 // the empty prefix — drop it and subarrays starting at 0 vanish4 var prefix = 05 var count = 06 for (num in nums) {7 prefix += num8 val need = prefix - k9 count += prefixCounts.getOrDefault(need, 0)10 prefixCounts[prefix] = prefixCounts.getOrDefault(prefix, 0) + 111 }12 return count13}
State
- i
- —
- prefix
- 0
- need
- —
- count
- 0
- k
- 2
- counts
- {0→1}
Why this step
Before anything is scanned the running sum is already 0, so that empty prefix is recorded once — skip this seed and every subarray starting at index 0 goes uncounted.
1 / 15
The same pattern also solves
Recognize the window, and these fall to the same skeleton.
- Continuous Subarray Sum (LC 523) ↗
Same prefix-sum map, but keyed on prefix % k — hunt equal remainders instead of equal sums.
- Subarray Sums Divisible by K (LC 974) ↗
The counting twin of 523 — count matching remainders exactly the way matching prefixes are counted here.
- Maximum Size Subarray Sum Equals k (LC 325) ↗
Store the first index per prefix instead of a count, because the goal flips from counting matches to maximizing length.