Count subarrays with given sum
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Given an integer array nums and an integer k, return the number of contiguous subarrays whose elements sum to exactly k. A subarray is a non-empty, contiguous slice nums[i..j] with i ≤ j. Elements may be negative or zero, so the same value can be reached by many different slices, and a slice can “overshoot” k and come back to it.
Constraints: 1 ≤ n ≤ 2 × 10^4, -1000 ≤ nums[i] ≤ 1000, -10^7 ≤ k ≤ 10^7 on LeetCode. The code handles n = 0 and uses long for sums so that larger values are also safe.
Example 1
nums = [1, 1, 1], k = 2
answer = 2 // [1, 1] at indices 0..1 and at indices 1..2
Example 2
nums = [1, 2, 3], k = 3
answer = 2 // [1, 2] and [3]
Example 3
nums = [1, -1, 0], k = 0
answer = 3 // [1, -1], [0] and [1, -1, 0]
Because negatives are allowed, the sliding-window technique that works for positive arrays does not apply here: growing the window can make the sum go down, so there is no monotonic quantity to slide on.
Approach 1: Brute force
Intuition. A subarray is determined by its two endpoints. Enumerate every pair (i, j) with i ≤ j, add up the elements in between, and count the pairs where the sum is k. This is the definition written as code and is the baseline the faster methods must agree with.
Algorithm.
- For every start
iand every endj ≥ i: - Compute
sum = nums[i] + ... + nums[j]with a third loop. - If
sum == k, increment the counter.
static int bruteForce(int[] nums, int k) {
int n = nums.length, count = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
long sum = 0;
for (int p = i; p <= j; p++) sum += nums[p];
if (sum == k) count++;
}
}
return count;
}
Complexity. Time O(n^3): O(n^2) pairs, each summed in O(n). Space O(1).
Approach 2: Better, running sum per start index
Intuition. The inner summation repeats work: the sum of nums[i..j] is the sum of nums[i..j-1] plus nums[j]. So for a fixed start i, extend the end j one step at a time and maintain the sum incrementally. This removes one whole loop.
Algorithm.
- For every start
i: setsum = 0. - For every end
jfromiton - 1:sum += nums[j]; ifsum == k, increment the counter.
static int better(int[] nums, int k) {
int n = nums.length, count = 0;
for (int i = 0; i < n; i++) {
long sum = 0;
for (int j = i; j < n; j++) {
sum += nums[j];
if (sum == k) count++;
}
}
return count;
}
Complexity. Time O(n^2): each pair (i, j) is now processed in constant time. Space O(1).
Note that we cannot break when sum > k, because a later negative element could bring the sum back down to k. That is precisely why this problem is harder than its all-positive cousin.
Approach 3: Optimal, prefix sums and a hash map
Intuition. Let prefix[j] be the sum of the first j elements (prefix[0] = 0). The sum of the subarray nums[i..j-1] is prefix[j] - prefix[i]. So the question “how many subarrays ending just before j sum to k?” becomes “how many earlier prefixes prefix[i] equal prefix[j] - k?” That is a counting question about values we have already seen, and a hash map from prefix value to its number of occurrences answers it in O(1).
Walk left to right, maintaining the running prefix. At each step, look up how many times prefix - k has appeared, add that to the answer, then record the current prefix in the map. The initial entry freq[0] = 1 represents the empty prefix, which is what lets subarrays that start at index 0 be counted.
This is the same “have I seen the complement before?” idea as in Two Sum, applied to prefix sums instead of raw values. Because the map counts all earlier occurrences, repeated prefix values (which happen whenever a stretch sums to zero) are handled correctly.
Algorithm.
- Create
freqwithfreq[0] = 1; setprefix = 0,count = 0. - For each
x:prefix += x;count += freq[prefix - k](zero if absent);freq[prefix]++. - Return
count.
static int optimal(int[] nums, int k) {
Map<Long, Integer> freq = new HashMap<>(); // prefix sum -> how many times it has occurred so far
freq.put(0L, 1); // the empty prefix, so subarrays starting at index 0 are counted
long prefix = 0;
int count = 0;
for (int x : nums) {
prefix += x;
count += freq.getOrDefault(prefix - k, 0);
freq.merge(prefix, 1, Integer::sum);
}
return count;
}
Dry run on nums = [1, -1, 0], k = 0:
| x | prefix | prefix - k | freq[prefix - k] | count | freq after |
|---|---|---|---|---|---|
| start | 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} |
The three counted subarrays are [1, -1] (prefix 0 at step 2 matched the empty prefix), and [0] plus [1, -1, 0] (prefix 0 at step 3 matched both earlier zeros).
Complexity. Time O(n): one pass with O(1) average-time map operations. Space O(n) for the map in the worst case (all prefixes distinct).
Why must we look up before inserting the current prefix? If we inserted first, then for k = 0 the current prefix would match itself and count an empty subarray, which is not allowed. Insert after the lookup, exactly as Two Sum inserts after checking.
Edge cases and pitfalls
k = 0and runs of zeros.[0, 0, 0, 0]has10subarrays summing to0, one for each(i, j)pair. The frequency map counts repeated prefix values, so this comes out right; a map from prefix to a single index would undercount.- Negative numbers. They are the reason sliding windows fail and the reason prefix values can repeat. All three approaches handle them without special cases.
- Empty array. The answer is
0. The map starts with the empty prefix, but no lookups happen, so nothing is counted. - Overflow. Prefix sums grow with
n; withn = 10^5and values up to10^9they exceedint. The code keepsprefixand the map keys aslong. The test with three values of magnitude10^9exercises this. - Lookup/insert order. Look up
prefix - k, then insertprefix. Reversing the order miscounts whenk = 0. - Counting versus finding. The related problem “longest subarray with sum k” stores the first index of each prefix instead of a count. Do not mix the two: counting needs frequencies, length needs earliest positions.
- Return type. The count can reach
n(n+1)/2, which exceedsintfornabove about65,000when every subarray matches (all zeros withk = 0). The method follows the usualintsignature; switchcounttolongif constraints make that possible.
Related problems
- Two Sum is the same complement-lookup trick on raw values instead of prefix sums.
- Longest Consecutive Sequence in an Array also replaces a linear search with a hash lookup.
- Pascal’s Triangle I is the next problem in this section.