Subarray Sum Equals K
Subarray Sum Equals K: count the contiguous subarrays whose elements sum to exactly k. The array may contain negative numbers.
- 1 <= nums.length <= 2 * 10⁴
- -1000 <= nums[i] <= 1000
- -10⁷ <= k <= 10⁷
Intuition
Subarray sum equals k counts contiguous subarrays summing to exactly k. The array may contain negative numbers, and that single fact rules out the sliding window.
A window works only when extending it moves the sum predictably. With negatives, adding an element can decrease the sum, so shrinking and growing no longer have reliable effects — the technique simply does not apply.
Prefix sums do apply, and the reframing is the whole solution:
- A subarray from i+1 to j sums to k exactly when prefix[j] − prefix[i] = k, so for each position the question becomes how many earlier prefixes equal currentSum − k.
That converts a search over subarrays into a hash-map lookup, giving one pass instead of nested loops.
The map stores how many times each prefix sum has occurred, not merely whether it has. Several earlier positions can share a prefix sum, and each represents a distinct valid subarray, so counting is essential — a set undercounts badly.
Seeding the map with {0: 1} is what allows subarrays starting at index 0 to be found. Without it, any subarray beginning at the first element is missed, which is the standard bug across this whole family of problems.
The order inside the loop matters: look up before inserting the current prefix sum. Inserting first lets a zero-length subarray match when k is 0.
The brute force over all subarrays is O(n²), which is what the hash map replaces.
One pass gives O(n) time and O(n) space.
Contiguous-sum questions over an array that may contain negatives point to prefix sums with a hash map, not a sliding window. The presence of negative values is the deciding signal, because it removes the monotonicity a window relies on. Continuous Subarray Sum and Subarray Sums Divisible by K are the same idea modulo an extra step.
Approach
Before reading on: express the sum of a subarray as a difference of two prefix sums, then rearrange to see what you should be looking up. Work out why a set of seen prefixes is not enough, and what the map must be seeded with.
Rule out the sliding window
Negative numbers mean adding an element can decrease the sum, so growing and shrinking a window have no reliable effect. The technique does not apply.
Reframe with prefix sums
A subarray sums to k exactly when prefix[j] − prefix[i] = k, so each position asks how many earlier prefixes equal currentSum − k.
Store counts, not membership
Several positions can share a prefix sum, each giving a distinct subarray. A set undercounts — the map must hold occurrence counts.
Seed the map with zero
Initialise with {0: 1} so subarrays starting at index 0 are found. Omitting this is the standard bug across the whole prefix-sum family.
Look up before inserting
Query for currentSum − k before recording the current prefix. Inserting first lets a zero-length subarray match when k is 0.
Accumulate the count
Add the map's value for currentSum − k to the running total — each earlier occurrence is one more valid subarray ending here.
Cost of the approach
One pass with O(1) map operations gives O(n) time and O(n) space, replacing the O(n²) enumeration of all subarrays.
Solution & live demo
Common pitfalls
Omitting the {0: 1} seed
seen = defaultdict(int) prefix = 0
seen = defaultdict(int) seen[0] = 1
Any subarray starting at index 0 needs a prior prefix of 0 to be counted. Without the seed, [3] with k = 3 returns 0 instead of 1.
Storing prefixes in a set
if prefix - k in seen:
count += 1count += seen[prefix - k]
The same prefix sum can occur at multiple indices, each starting a distinct valid subarray. A set collapses them, undercounting whenever a prefix repeats.
Recording the current prefix before the lookup
seen[prefix] += 1 count += seen[prefix - k]
count += seen[prefix - k] seen[prefix] += 1
When k is 0 the current prefix would match itself, counting an empty subarray that does not exist. The lookup must see only prefixes from strictly earlier positions.
Edge cases
The {0: 1} seed is what makes it countable.
A prefix repeating itself counts, so zero-sum subarrays are found.
Prefix sums handle them; a sliding window would not.
No lookup ever hits and the count stays 0.
Each is counted separately, since each has its own starting prefix.