21. Count subarrays whose sum equals k.
Given an integer array and target k, return the number of contiguous subarrays whose values sum to k. Explain negative values, repeated prefix sums, empty input behavior, and complexity.
I would use a running prefix sum and a hash map that stores how many times each earlier prefix sum has appeared. I start the map with 0 mapped to 1. For each number, I update the prefix sum, look for prefix_sum minus k, add that frequency to the answer, and then record the current prefix sum. This handles negative values and repeated prefix sums. The solution takes O(n) expected time and O(n) auxiliary space.
See the Code while reading this explanation.
The problem asks for the number of contiguous subarrays whose values add up to k. A contiguous subarray contains neighboring elements. I use a running prefix sum and a hash map of prefix-sum frequencies. This method works with positive, negative, and zero values because it does not depend on the sum moving in only one direction.
- What input sizes, value ranges, and edge cases should the solution handle?
- What output should be returned for empty, invalid, or duplicate input?
- Should I prioritize execution time or memory use, and may I use the standard library?
The input is an integer array nums and an integer k. The output is one integer. It is the number of contiguous subarrays whose sum equals k.
For the diagram example, nums = [1, -1, 1, 1] and k = 1. The expected answer is 5. The valid subarrays are [0..0], [0..2], [2..2], [1..3], and [3..3].
I keep a running prefix sum. A prefix sum is the total of all values processed so far.
Suppose the current prefix sum is S. If an earlier prefix sum equals S - k, then the values between that earlier position and the current position add up to k.
The hash map is named prefix_count. Each key is a prefix sum value. Each value is the number of times that prefix sum has appeared earlier.
The central invariant is that prefix_count stores frequencies of prefix sums from earlier positions only.
Set prefix_sum = 0 and count = 0.
Set prefix_count = {0: 1}. The zero represents the empty prefix before the array starts. This lets the algorithm count a valid subarray that begins at index 0.
Traversal begins at index 0.
Before processing any element, prefix_sum = 0, count = 0, and prefix_count = {0: 1}.
At index 0, num is 1. Update prefix_sum to 1. Compute needed = 1 - 1 = 0. Before this step, prefix_count is {0: 1}. The needed value appears once, so add 1 to count. Count becomes 1. Then record prefix sum 1. The map becomes {0: 1, 1: 1}. This step counts [0..0].
At index 1, num is -1. Update prefix_sum to 0. Compute needed = 0 - 1 = -1. Before this step, prefix_count is {0: 1, 1: 1}. The needed value does not appear, so count stays 1. Then increment the frequency of prefix sum 0. The map becomes {0: 2, 1: 1}.
At index 2, num is 1. Update prefix_sum to 1. Compute needed = 1 - 1 = 0. Before this step, prefix_count is {0: 2, 1: 1}. Prefix sum 0 has appeared twice, so add 2 to count. Count becomes 3. Then increment the frequency of prefix sum 1. The map becomes {0: 2, 1: 2}. This step counts [0..2] and [2..2].
At index 3, num is 1. Update prefix_sum to 2. Compute needed = 2 - 1 = 1. Before this step, prefix_count is {0: 2, 1: 2}. Prefix sum 1 has appeared twice, so add 2 to count. Count becomes 5. Then record prefix sum 2. The map becomes {0: 2, 1: 2, 2: 1}. This step counts [1..3] and [3..3].
All four elements have been processed. Return 5.
At each index, let the current prefix sum be S. Every earlier prefix sum equal to S - k defines one contiguous subarray ending at the current index whose sum is k.
The map stores the frequency of each earlier prefix sum. Therefore, prefix_count[S - k] is exactly the number of valid subarrays ending at the current index.
The algorithm adds matches before recording the current prefix sum. This means the current position cannot be treated as an earlier prefix. Each valid subarray is counted exactly once at its ending index.
The code creates prefix_count with {0: 1}. It also initializes prefix_sum and count to zero.
For each number, it first adds the number to prefix_sum. It then evaluates prefix_sum - k and uses dictionary get to find how many times that needed prefix sum appeared earlier. That frequency is added to count.
After counting matches, the code increments the frequency of the current prefix sum. When the loop ends, it returns count.
The expected time is O(n). The loop processes each array element once. Python dictionary lookup and insertion take O(1) time on average.
The auxiliary space is O(n). In the worst input shape, the dictionary may contain a different prefix sum for many positions.
An empty array returns 0. Negative values work. Repeated prefix sums correctly create multiple matches. Zero and duplicate values also work.
The key insight is that a subarray sum can be written as the difference between two prefix sums. If the current prefix sum is S, an earlier prefix sum S - k creates a subarray ending at the current index with sum k. The hash map stores prefix sum value to frequency. Its invariant is that it contains counts of earlier prefix sums only. Frequencies are required because the same prefix sum may appear more than once, and each occurrence creates a different valid subarray. This method is more suitable than a sliding window because negative values can make a window sum rise or fall unpredictably.
from typing import List
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
# Map each earlier prefix sum to the number of times it appeared.
# The empty prefix has sum 0 and appears once before index 0.
prefix_count = {0: 1}
# Store the sum of all values processed so far.
prefix_sum = 0
# Store the number of valid contiguous subarrays found.
count = 0
# Process every array element from left to right.
for num in nums:
# Add the current value to the running prefix sum.
prefix_sum += num
# Every earlier prefix equal to prefix_sum - k creates
# one valid subarray ending at the current position.
count += prefix_count.get(prefix_sum - k, 0)
# Record the current prefix only after counting matches.
prefix_count[prefix_sum] = prefix_count.get(prefix_sum, 0) + 1
# Return the total number of matching contiguous subarrays.
return count
if __name__ == "__main__":
nums = [1, -1, 1, 1]
k = 1
answer = Solution().subarraySum(nums, k)
print(answer) # Expected output: 5Let n be the number of elements in nums. The expected time is O(n) because the code processes each element once. Each Python dictionary lookup and insertion takes O(1) time on average. This is expected time rather than a guaranteed worst-case bound for hashing. The auxiliary space is O(n) because the dictionary may store many different prefix sums. The empty input still uses only the initial map entry and returns 0.
This pattern is useful when software must count contiguous ranges with a required total. Examples include transaction changes, sensor readings, inventory movements, score changes, and event measurements. It is especially useful when the values may be positive, negative, or zero, because a normal sliding window is not reliable in that case.
This question tests whether the candidate recognizes the prefix-sum and hash-map frequency pattern. It checks whether they can derive the relationship between the current prefix sum and an earlier prefix sum. It also tests handling of negative values, repeated prefix sums, and subarrays that begin at index 0. The interviewer is evaluating update order, dictionary usage, correctness reasoning, edge-case awareness, and accurate expected-time complexity language.
A common mistake is using a sliding window even though negative values are allowed. Another mistake is forgetting to initialize prefix_count with {0: 1}, which misses valid subarrays that start at index 0. Some candidates record the current prefix sum before counting matches. The lookup must happen first so the map represents earlier prefixes only. Another mistake is storing only whether a prefix sum exists. The map must store frequencies because repeated prefix sums can create multiple valid subarrays. It is also incorrect to claim guaranteed O(n) time because Python dictionary operations are O(1) on average.
State the invariant before writing code: prefix_count stores the frequency of every prefix sum seen before the current position. Then explain that matches are counted before the current prefix sum is recorded.









