A clear explanation of Subarray Sum Equals K using prefix sums and a hash map to count matching subarrays in linear time.
Problem Restatement
We are given an integer array nums and an integer k.
Return the total number of non-empty contiguous subarrays whose sum is exactly equal to k.
A subarray must use consecutive elements.
The problem constraints are 1 <= nums.length <= 2 * 10^4, -1000 <= nums[i] <= 1000, and -10^7 <= k <= 10^7. The array may contain positive numbers, negative numbers, and zero.
Input and Output
| Item | Meaning |
|---|---|
| Input | An integer array nums and an integer k |
| Output | Number of contiguous subarrays with sum k |
| Subarray | Non-empty and contiguous |
| Important detail | nums can contain negative numbers |
Example function shape:
def subarraySum(nums: list[int], k: int) -> int:
...Examples
Example 1:
nums = [1, 1, 1]
k = 2The valid subarrays are:
nums[0:2] = [1, 1]
nums[1:3] = [1, 1]Both have sum 2.
So the answer is:
2Example 2:
nums = [1, 2, 3]
k = 3The valid subarrays are:
[1, 2]
[3]So the answer is:
2Example 3:
nums = [1, -1, 0]
k = 0The valid subarrays are:
[1, -1]
[0]
[1, -1, 0]So the answer is:
3First Thought: Check Every Subarray
A direct solution is to try every possible starting index and every possible ending index.
For each subarray, compute its sum and check whether it equals k.
A basic version looks like this:
class Solution:
def subarraySum(self, nums: list[int], k: int) -> int:
ans = 0
n = len(nums)
for start in range(n):
total = 0
for end in range(start, n):
total += nums[end]
if total == k:
ans += 1
return ansThis avoids recomputing each subarray sum from scratch, but it still checks all O(n^2) subarrays.
With n up to 20000, this can be too slow.
Key Insight
Use prefix sums.
Let prefix[i] mean the sum of elements before index i.
Then the sum of a subarray from index j to index i is:
prefix[i + 1] - prefix[j]We want this to equal k:
prefix[i + 1] - prefix[j] == kRearrange it:
prefix[j] == prefix[i + 1] - kSo when we are at the current prefix sum current, we need to know:
current - kIf that prefix sum appeared before, then every previous occurrence gives one valid subarray ending at the current index.
Why a Hash Map Is Needed
We need to count how many times each prefix sum has appeared.
The map stores:
prefix_sum -> frequencyBefore reading any element, the prefix sum is 0.
So we start with:
count = {0: 1}This is important because a subarray starting at index 0 is valid when the current prefix sum itself equals k.
For example:
nums = [1, 2]
k = 3After reading both numbers, the current prefix sum is 3.
We need:
current - k = 3 - 3 = 0Since prefix sum 0 appeared once before the array started, [1, 2] is counted.
Algorithm
Initialize:
prefix = 0 ans = 0 count = {0: 1}For each number
numinnums:- Add it to the running prefix sum.
- Compute
need = prefix - k. - Add
count[need]to the answer. - Record the current prefix sum by incrementing
count[prefix].
The order matters.
We count previous prefix sums before inserting the current prefix sum. This prevents counting an empty subarray.
Correctness
At any index, let prefix be the sum of all elements from the start of the array through the current element.
A subarray ending at the current index has sum k exactly when there is an earlier prefix sum equal to:
prefix - kIf that earlier prefix sum occurred c times, then there are exactly c different starting positions for subarrays ending at the current index with sum k.
The algorithm adds exactly this c to the answer.
Then it records the current prefix sum for future subarrays.
Every valid subarray has one ending index. When the algorithm reaches that ending index, it counts the subarray using the prefix sum before the subarray starts. Therefore, every valid subarray is counted once.
The algorithm only counts subarrays whose prefix difference is exactly k, so it never counts an invalid subarray.
Complexity
Let n be the length of nums.
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan the array once |
| Space | O(n) | The hash map may store many distinct prefix sums |
Implementation
from collections import defaultdict
class Solution:
def subarraySum(self, nums: list[int], k: int) -> int:
count = defaultdict(int)
count[0] = 1
prefix = 0
ans = 0
for num in nums:
prefix += num
need = prefix - k
ans += count[need]
count[prefix] += 1
return ansCode Explanation
We create a frequency map for prefix sums:
count = defaultdict(int)
count[0] = 1The initial 0 prefix represents the sum before the array starts.
Then we track the running sum:
prefix = 0For each number:
prefix += numwe compute the prefix sum needed to form a subarray sum of k:
need = prefix - kThen we add the number of previous times that prefix sum appeared:
ans += count[need]Finally, we record the current prefix sum:
count[prefix] += 1This makes it available for later subarrays.
Testing
def run_tests():
s = Solution()
assert s.subarraySum([1, 1, 1], 2) == 2
assert s.subarraySum([1, 2, 3], 3) == 2
assert s.subarraySum([1, -1, 0], 0) == 3
assert s.subarraySum([0, 0, 0], 0) == 6
assert s.subarraySum([-1, -1, 1], 0) == 1
assert s.subarraySum([3, 4, 7, 2, -3, 1, 4, 2], 7) == 4
print("all tests passed")
run_tests()| Test | Why |
|---|---|
[1, 1, 1], k = 2 | Official sample |
[1, 2, 3], k = 3 | Counts both multi-element and single-element subarrays |
[1, -1, 0], k = 0 | Handles negative numbers and zero |
[0, 0, 0], k = 0 | Many overlapping valid subarrays |
[-1, -1, 1], k = 0 | Negative prefix sums |
[3, 4, 7, 2, -3, 1, 4, 2], k = 7 | Multiple matches with repeated prefix logic |