The problem
You get an integer array nums (values may be negative) and an integer k. Return how many contiguous subarrays have a sum of exactly k.
Input: nums = [1, 1, 1], k = 2
Output: 2The subarrays [1, 1] at indices 0–1 and [1, 1] at indices 1–2 both sum to 2.
How to think about it
Start with brute force. Try every start i, and extend the end j while keeping a running sum. Count every time it equals k. That’s O(n²), which is too slow for 2 × 10⁴ elements in the worst case.
Why a sliding window doesn’t work. A window works when growing it always raises the sum and shrinking it always lowers it. With negative numbers that’s no longer true, so you can’t decide which end to move. You need a different idea.
The key insight: a subarray sum is a difference of two prefix sums. Let prefix[j] be the sum of the first j elements. The subarray from i to j − 1 sums to prefix[j] − prefix[i]. You want that to equal k, which means prefix[i] = prefix[j] − k. So as you walk the array keeping a running sum s, the number of subarrays ending here with sum k is just how many earlier prefix sums equal s − k. A hash map of prefix-sum counts answers that in O(1). This is Two Sum, applied to prefix sums.
Seed the map with {0: 1}. The empty prefix (before any element) has sum 0. Without it, subarrays that start at index 0 would never be counted.
Which pattern is this? Hash map plus prefix sums. The tell is “count subarrays with sum exactly k”, especially when values can be negative.
Approach
- Create
cnt, a map from prefix sum to how many times it has occurred, and setcnt[0] = 1. - Keep a running sum
s = 0and an answerres = 0. - For each
x: add it tos; addcnt[s − k]tores; then recordswithcnt[s] += 1. - Return
res.
Solution
from collections import defaultdict
def subarray_sum(nums, k):
cnt = defaultdict(int); cnt[0] = 1; s = 0; res = 0 # empty prefix has sum 0
for x in nums:
s += x; res += cnt[s - k]; cnt[s] += 1 # earlier prefixes equal to s-k
return res
# subarray_sum([1,1,1], 2) -> 2The order inside the loop matters: look up s − k before recording s, so that when k = 0 a prefix isn’t matched with itself.
Dry run
On nums = [1, 1, 1], k = 2 (the map shows only non-zero counts; defaultdict also stores 0 for keys it has looked up):
x |
s |
s − k |
cnt[s − k] |
res |
cnt after recording |
|---|---|---|---|---|---|
| — | 0 | — | — | 0 | {0: 1} |
| 1 | 1 | −1 | 0 | 0 | {0: 1, 1: 1} |
| 1 | 2 | 0 | 1 | 1 | {0: 1, 1: 1, 2: 1} |
| 1 | 3 | 1 | 1 | 2 | {0: 1, 1: 1, 2: 1, 3: 1} |
It returns 2. On [1, 2, 3] with k = 3 it also returns 2 ([1, 2] and [3]), and on [1, −1, 0] with k = 0 it returns 3.
Complexity
- Time: O(n). One pass, with O(1) average map operations per element.
- Space: O(n). Up to n + 1 distinct prefix sums in the map.
Edge cases and pitfalls
- Forgetting
cnt[0] = 1misses every subarray that starts at index 0. On[3]withk = 3, you’d get 0 instead of 1. - Negative values and zeros: handled naturally; they’re exactly why the sliding window fails.
k = 0: looking up before recording is what keeps the count right.- Using a set instead of counts loses multiplicity. With zeros in the array, the same prefix sum repeats and each occurrence is a separate subarray.
- Empty array: returns 0.
Related problems
- Two Sum (LeetCode 1): the same “look up the complement” move, on raw values.
- Contiguous Array (LeetCode 525): map 0 to −1, then find equal prefix sums.
- Subarray Sums Divisible by K (LeetCode 974): the same counting, keyed on prefix sum mod k.
- 1.Two Sum (LeetCode 1)
- 2.Valid Anagram (LeetCode 242)
- 3.Contains Duplicate (LeetCode 217)
- 4.Ransom Note (LeetCode 383)
- 5.First Unique Character in a String (LeetCode 387)
- 6.Majority Element (LeetCode 169)
- 7.Isomorphic Strings (LeetCode 205)
- 8.Group Anagrams (LeetCode 49)
- 9.Top K Frequent Elements (LeetCode 347)
- 10.Longest Consecutive Sequence (LeetCode 128)
- 11.Subarray Sum Equals K (LeetCode 560)
- 12.Valid Sudoku (LeetCode 36)
- 13.LRU Cache (LeetCode 146)