Count subarrays with a given sum
Given an array nums of integers, which may be negative, and an integer
k, return the number of contiguous subarrays whose elements sum to exactly
k.
Subarrays are counted by position, so two subarrays covering different index ranges count separately even if they hold the same values.
Examples
| Input | Output | Why |
|---|---|---|
nums = [1, 1, 1], k = 2 | 2 | Indices 0–1 and 1–2. |
nums = [3, 4, 7, 2, -3, 1, 4, 2], k = 7 | 4 | [3,4], [7], [7,2,-3,1], [1,4,2]. The negative value is why a window does not work. |
nums = [1, -1, 0], k = 0 | 3 | [1,-1], [1,-1,0] and [0]. |
Constraints
- 1 ≤ n ≤ 2 × 10⁴
- −1000 ≤ nums[i] ≤ 1000
- −10⁷ ≤ k ≤ 10⁷
- Values may be negative or zero
Hints
Hint 1
A sliding window needs the sum to grow as the window grows. Check whether that holds when the array contains negative numbers.
Hint 2
Let P[i] be the sum of the first i elements. The sum of the range (i, j] is P[j] − P[i]. Rewrite the condition you want in terms of P.
Hint 3
You need P[j] − P[i] = k, so P[i] = P[j] − k. Sweep j, and at each step ask how many earlier prefixes had the value P[j] − k. A hash map of counts answers that in O(1).
Dry run on [3,4,7,2,-3,1,4,2],7
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 10nums30417223-34154627prefix0total0k7
The map starts as {0: 1} — the empty prefix. Without it, every subarray starting at index 0 is missed.
-
Step 2 / 10nums30j417223-34154627prefix3want-4found0total0
Take 3: prefix = 3. Looking for an earlier prefix of -4 — none, so nothing ends here. Total 0.
-
Step 3 / 10nums3041j7223-34154627prefix7want0found1total1
Take 4: prefix = 7. Looking for an earlier prefix of 0 — found 1, so 1 subarray ends here. Total 1.
-
Step 4 / 10nums304172j23-34154627prefix14want7found1total2
Take 7: prefix = 14. Looking for an earlier prefix of 7 — found 1, so 1 subarray ends here. Total 2.
-
Step 5 / 10nums30417223j-34154627prefix16want9found0total2
Take 2: prefix = 16. Looking for an earlier prefix of 9 — none, so nothing ends here. Total 2.
-
Step 6 / 10nums30417223-34j154627prefix13want6found0total2
Take -3: prefix = 13. Looking for an earlier prefix of 6 — none, so nothing ends here. Total 2.
-
Step 7 / 10nums30417223-3415j4627prefix14want7found1total3
Take 1: prefix = 14. Looking for an earlier prefix of 7 — found 1, so 1 subarray ends here. Total 3.
-
Step 8 / 10nums30417223-341546j27prefix18want11found0total3
Take 4: prefix = 18. Looking for an earlier prefix of 11 — none, so nothing ends here. Total 3.
-
Step 9 / 10nums30417223-34154627jprefix20want13found1total4
Take 2: prefix = 20. Looking for an earlier prefix of 13 — found 1, so 1 subarray ends here. Total 4.
-
Step 10 / 10nums30417223-34154627total4
One pass, one map lookup per element. Answer 4.
How to think about it
This is the problem usually listed as Subarray Sum Equals K.
The instinct is a sliding window, and it is wrong here. A window works when extending it increases the sum, so that growing and shrinking move the total in known directions. With negative numbers that monotonicity is gone — extending can reduce the sum — and the window has no rule to follow.
Prefix sums remove the need for it. Write P[j] for the sum of the first j
elements, with P[0] = 0. Any subarray is a difference of two prefixes:
sum(i .. j-1) = P[j] − P[i]
So a subarray ending at j sums to k exactly when some earlier prefix
equals P[j] − k. Sweep j left to right keeping a map from prefix value to
how many times it has occurred, and at each step add the count of P[j] − k to the
answer.
Two details decide whether it is right. The map must start holding {0: 1}, standing for
the empty prefix — without it you miss every subarray that starts at index 0. And it must store
counts, not presence, because the same prefix value can occur many times and each occurrence is
a distinct subarray.
Complexity. Time O(n) · Space O(n)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def count_subarrays(nums, k):
counts = {0: 1} # the empty prefix, so subarrays starting at index 0 are counted
prefix = total = 0
for x in nums:
prefix += x
total += counts.get(prefix - k, 0) # look up first...
counts[prefix] = counts.get(prefix, 0) + 1 # ...then record this prefix
return total#include <unordered_map>
#include <vector>
using namespace std;
int solve(vector<int> nums, long long k) {
unordered_map<long long, int> counts;
counts[0] = 1; // the empty prefix
long long prefix = 0;
int total = 0;
for (int x : nums) {
prefix += x;
auto it = counts.find(prefix - k);
if (it != counts.end()) total += it->second; // look up first...
counts[prefix]++; // ...then record
}
return total;
}import java.util.*;
class Solution {
public int countSubarrays(int[] nums, long k) {
Map<Long, Integer> counts = new HashMap<>();
counts.put(0L, 1); // the empty prefix
long prefix = 0;
int total = 0;
for (int x : nums) {
prefix += x;
total += counts.getOrDefault(prefix - k, 0); // look up first...
counts.merge(prefix, 1, Integer::sum); // ...then record
}
return total;
}
}function countSubarrays(nums, k) {
const counts = new Map([[0, 1]]); // the empty prefix
let prefix = 0, total = 0;
for (const x of nums) {
prefix += x;
total += counts.get(prefix - k) || 0; // look up first...
counts.set(prefix, (counts.get(prefix) || 0) + 1); // ...then record
}
return total;
}use std::collections::HashMap;
fn solve(nums: &[i32], k: i64) -> i32 {
let mut counts: HashMap<i64, i32> = HashMap::new();
counts.insert(0, 1); // the empty prefix
let mut prefix: i64 = 0;
let mut total = 0;
for &x in nums {
prefix += x as i64;
total += *counts.get(&(prefix - k)).unwrap_or(&0); // look up first...
*counts.entry(prefix).or_insert(0) += 1; // ...then record
}
total
}import qualified Data.Map.Strict as M
import Data.List (scanl')
solve :: [Int] -> Int -> Int
solve xs k = go (M.singleton 0 1) 0 (scanl' (+) 0 xs)
where
-- walk the prefix sums, counting how many earlier prefixes equal (p - k)
go _ acc [] = acc
go _ acc [_] = acc
go m acc (_:p:ps) =
let hit = M.findWithDefault 0 (p - k) m
m' = M.insertWith (+) p 1 m
in go m' (acc + hit) (p:ps)
Where people lose marks
- Reaching for a sliding window. It is correct only when every value is non-negative; with a negative number present it silently returns the wrong count.
- Forgetting to seed the map with
{0: 1}. Everything still runs, and every subarray starting at index 0 is missed. - Storing a set instead of counts, which collapses repeated prefix values and undercounts.
- Recording the current prefix in the map before looking up
P[j] − k. Whenk = 0that counts the empty subarray and inflates the answer. Look up first, then insert.
Variants to try
- Return the longest such subarray rather than the count. (Store the first index at which each prefix value appeared, not how many times.)
- All values non-negative — does anything simpler work? (Yes: a sliding window, in O(1) extra space.)
- Count subarrays whose sum is divisible by k. (Key the map on the prefix sum modulo k, taking care with negative remainders.)
Next problems
Pair with a given sum in an unsorted array
Every pair is n² work. Remembering what you have already passed makes it n, and the order of two lines decides whether it is right.
Open the problem → Medium15 minLongest run of consecutive integers
Sorting gives the answer in O(n log n). A set gives it in O(n), and the reason it is not O(n²) is the interesting part.
Open the problem → Medium15 minThe k most frequent values
A hash map does the counting in one pass. The interesting part is ordering what you counted, and saying precisely how ties break.
Open the problem →Write a solution and run it against the real test table.
Keep going
Pair with a given sum in an unsorted array
Every pair is n² work. Remembering what you have already passed makes it n, and the order of two lines decides whether it is right.
Open the problem → Medium15 minLongest run of consecutive integers
Sorting gives the answer in O(n log n). A set gives it in O(n), and the reason it is not O(n²) is the interesting part.
Open the problem → Medium15 minThe k most frequent values
A hash map does the counting in one pass. The interesting part is ordering what you counted, and saying precisely how ties break.
Open the problem → Arrays and hashingThe pattern behind it
Trade memory for time: a hash map answers "have I seen this?" in O(1), and most array problems reduce to asking it the right question.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.