Find the number of subarrays in an integer array that sum to k.
Input: nums = [1, 2, -1, 1, 2], k = 3
Output: 3
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Imagine cumulative mile markers along a highway: 0, 3, 4, 6, 5, 9… A stretch of road is exactly k miles long whenever two markers differ by k. So instead of measuring every possible stretch, you walk forward once, and at each marker you ask: “have I already passed a marker that is exactly k behind me?” Count those — that is the number of k-length stretches ending here.
The brute force solution to this problem involves iterating through every possible subarray and checking if their sum equals k. It takes time to iterate over all subarrays, and finding the sum of each subarray takes time, resulting in an overall time complexity of , where denotes the length of the array. This solution is quite inefficient, so let’s think of something better.
Since we’re working with subarray sums, it’s worth considering how prefix sums can be used to solve this problem.
Prefix sums
As described in the Sum Between Range problem in this chapter, the sum of a subarray between two indexes, i and j, can be calculated with the following formula:
For subarrays which start at the beginning of the array (i.e., when i = 0), the formula is just:
In this problem, we already know the sum we’re looking for (k), meaning our goal is to find:
i and j such that prefix_sum[j] - prefix_sum[i - 1] == k when i > 0.j such that prefix_sum[j] == k when i == 0.We can unify both cases by recognizing that the formula prefix_sum[j] == k is the same as the formula prefix_sum[j] - prefix_sum[i - 1] == k when prefix_sum[i - 1] equals 0 (i.e., prefix_sum[j] - 0 == k).
One issue with this is when i == 0, index i - 1 is invalid. To make this unification possible while avoiding the out-of-bounds issue, we can prepend '[0]' to the prefix sums array, making it possible for prefix_sum[i - 1] to equal 0 when i - 1 == 0.
Keep in mind that we should iterate over the array from index 1 because we added this 0 to the start of the prefix sum array.
Here’s the code snippet for this approach:
from typing import List
def k_sum_subarrays(nums: List[int], k: int) -> int:
n = len(nums)
count = 0
# Populate the prefix sum array, setting its first element to 0.
prefix_sum = [0]
for i in range(0, n):
prefix_sum.append(prefix_sum[-1] + nums[i])
# Loop through all valid pairs of prefix sum values to find all subarrays that sum
# to 'k'.
for j in range(1, n + 1):
for i in range(1, j + 1):
if prefix_sum[j] - prefix_sum[i - 1] == k:
count += 1
return countint kSumSubarrays(vector<int>& nums, int k) {
int count = 0;
long long prefixSum = 0;
unordered_map<long long, int> counts;
counts[0] = 1;
for (int num : nums) {
prefixSum += num;
count += counts[prefixSum - k];
counts[prefixSum]++;
}
return count;
}int kSumSubarrays(int[] nums, int k) {
int count = 0;
long prefixSum = 0;
Map<Long, Integer> counts = new HashMap<>();
counts.put(0L, 1);
for (int num : nums) {
prefixSum += num;
count += counts.getOrDefault(prefixSum - k, 0);
counts.put(prefixSum, counts.getOrDefault(prefixSum, 0) + 1);
}
return count;
}This is an improvement on the brute force solution, which reduces the time complexity to . Can we optimize this solution further?
Optimization - hash map
An important point is that we don't need to treat both prefix_sum[j] and prefix_sum[i - 1] as unknowns in the formula. If we know the value of prefix_sum[j], we can find prefix_sum[i - 1] using prefix_sum[i - 1] = prefix_sum[j] - k.
Therefore, for each prefix sum (curr_prefix_sum), we need to find the number of times curr_prefix_sum - k previously appeared as a prefix sum before.
This is similar to the problem presented in Pair Sum - Unsorted in the Hash Maps and Sets chapter, where we learn a hash map is useful for implementing the above idea efficiently. In this context, if we store encountered prefix sum values in a hash map, we can check if curr_prefix_sum - k was encountered before in constant time.
Note, it's also important to track the frequency of each prefix sum we encounter using the hash map, as the same prefix sum may appear multiple times.
Let’s try using a hash map (prefix_sum_map) on the example below with k = 3. Initialize prefix_sum_map with one zero for the same reason we prepended 0 to the prefix sum array in the solution discussed earlier:
We’re using a hash map to keep track of prefix sums, we no longer need a separate array to store each individual prefix sum.
Initially, the prefix sum (curr_prefix_sum) is equal to 1. Its complement, -2, is not in the hash map as illustrated below. So, we continue:
Store the (curr_prefix_sum, freq) pair (1, 1) in the hash map before moving to the next prefix sum.
The next curr_prefix_sum value is 3 (1 + 2). Its complement, 0, exists in the hash map with a frequency of 1. This means we found 1 subarray of sum k. So, we add 1 to our count:
Store the (curr_prefix_sum, freq) pair (3, 1) in the hash map before moving on to the next value.
We now have a strategy for processing each value in the array:
Update curr_prefix_sum by adding the current value of the array to it
If curr_prefix_sum - k exists in the hash map, add its frequency (prefix_sum_map[curr_prefix_sum - k]) to count
Add (curr_prefix_sum, freq) to the hash map. If the key is already present, increase its frequency; if not, set it to 1.
Repeat this process for the rest of the array:
Once we’ve processed all the prefix sum values, we return count, which stores the number of subarrays that sum to k.
from typing import List
def k_sum_subarrays_optimized(nums: List[int], k: int) -> int:
count = 0
# Initialize the map with 0 to handle subarrays that sum to 'k' from the start of
# the array.
prefix_sum_map = {0: 1}
curr_prefix_sum = 0
for num in nums:
# Update the running prefix sum by adding the current number
curr_prefix_sum += num
# If a subarray with sum 'k' exists, increment 'count' by the number of times
# it has been found.
if curr_prefix_sum - k in prefix_sum_map:
count += prefix_sum_map[curr_prefix_sum - k]
# Store the 'curr_prefix_sum' value in the hash map.
prefix_sum_map[curr_prefix_sum] = prefix_sum_map.get(curr_prefix_sum, 0) + 1
return count
Time complexity: The time complexity of k_sum_subarrays_optimized is because we iterate through each value in the nums array.
Space complexity: The space complexity is due to the space taken up by the hash map.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
[3, 1, 2, -1, 4], k = 3Sweep left to right. At each step: add to prefix_sum, look up prefix_sum − k in the map, then record prefix_sum. Start with counts = {0: 1}.
| num | prefix_sum | look for (prefix−k) | found | count | counts after |
|---|---|---|---|---|---|
| — | 0 | — | — | 0 | {0:1} |
| 3 | 3 | 0 | 1 | 1 | {0:1, 3:1} |
| 1 | 4 | 1 | 0 | 1 | {0:1, 3:1, 4:1} |
| 2 | 6 | 3 | 1 | 2 | {0:1, 3:1, 4:1, 6:1} |
| −1 | 5 | 2 | 0 | 2 | {…, 5:1} |
| 4 | 9 | 6 | 1 | 3 | {…, 9:1} |
The three hits correspond to the subarrays [3] (prefix 0 seen), [1,2] (prefix 3 seen), and [-1,4] (prefix 6 seen). Answer = 3 ✓
[1, -1, 0], k = 0With k = 0 we look for prefix_sum − 0 = prefix_sum — i.e. “have I seen this exact running total before?” The value 0 shows up repeatedly, so the map stores counts, not just presence.
| num | prefix_sum | look for (prefix−0) | found | count | counts after |
|---|---|---|---|---|---|
| — | 0 | — | — | 0 | {0:1} |
| 1 | 1 | 1 | 0 | 0 | {0:1, 1:1} |
| −1 | 0 | 0 | 1 | 1 | {0:2, 1:1} |
| 0 | 0 | 0 | 2 | 3 | {0:3, 1:1} |
At the last step counts[0] is already 2 (the seed plus the prefix after [1,-1]), so this single element adds 2 at once. The three subarrays are [1,-1], [1,-1,0], and [0]. Answer = 3 ✓
Take-away: a set would wrongly report 1 here — you need the frequency, because the same prefix sum can be reached many times and each occurrence is a distinct starting point.
defaultdict(int) returns 0 for unseen keys, so counts[prefix_sum - k] reads cleanly. Seed with counts[0] = 1.
unordered_map<long long,int> value-initializes missing keys to 0, so counts[prefixSum - k] is safe. Use long long for the prefix to avoid overflow.
getOrDefault(key, 0) plays the role of the default-0 lookup, and prefixSum is a long. Same three steps: add, look up, record.