A clear explanation of Maximum Size Subarray Sum Equals k using prefix sums and earliest-index hashing.
Problem Restatement
We are given an integer array nums and an integer k.
Return the maximum length of a contiguous subarray whose sum equals k.
If no such subarray exists, return 0.
A subarray must be contiguous. The official examples include nums = [1, -1, 5, -2, 3], k = 3, whose answer is 4, because [1, -1, 5, -2] sums to 3 and is the longest. The problem also notes that the total sum of the array fits in a 32-bit signed integer.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums and integer k |
| Output | Maximum length of a subarray whose sum is k |
| If none exists | Return 0 |
| Subarray rule | Elements must be contiguous |
| Values | Can include positive, negative, and zero |
Function shape:
def maxSubArrayLen(nums: list[int], k: int) -> int:
...Examples
Example 1:
nums = [1, -1, 5, -2, 3]
k = 3The subarray:
[1, -1, 5, -2]has sum:
1 + (-1) + 5 + (-2) = 3Its length is 4.
Output:
4Example 2:
nums = [-2, -1, 2, 1]
k = 1The subarray:
[-1, 2]has sum:
-1 + 2 = 1Its length is 2.
Output:
2Example 3:
nums = [1, 2, 3]
k = 7No subarray sums to 7.
Output:
0First Thought: Check Every Subarray
A direct solution checks every possible subarray.
For each start index, keep extending the end index and update the running sum.
answer = 0
for left in range(len(nums)):
total = 0
for right in range(left, len(nums)):
total += nums[right]
if total == k:
answer = max(answer, right - left + 1)This works.
But it costs O(n^2) time.
For large arrays, this is too slow.
Key Insight
Use prefix sums.
Let:
prefix[i]be the sum of elements before index i.
Then the sum of subarray nums[j + 1 ... i] can be written as:
current_prefix - earlier_prefixAt index i, suppose the current prefix sum is:
prefixWe want a previous prefix sum such that:
prefix - previous = kRearrange:
previous = prefix - kSo for each index, we only need to know whether we have seen prefix sum prefix - k before.
If yes, the subarray from after that previous index through current index sums to k.
Why Store the Earliest Index
We want the maximum length.
For the same prefix sum, an earlier index gives a longer subarray.
So we store only the first time each prefix sum appears.
Example:
nums = [1, -1, 5, -2, 3]
k = 3Prefix values:
| Index | Number | Prefix Sum |
|---|---|---|
| before array | none | 0 |
0 | 1 | 1 |
1 | -1 | 0 |
2 | 5 | 5 |
3 | -2 | 3 |
4 | 3 | 6 |
At index 3, prefix sum is 3.
We need:
3 - 3 = 0The earliest prefix sum 0 occurred before the array, at index -1.
So the subarray length is:
3 - (-1) = 4That gives the answer.
Algorithm
- Create a dictionary
first_index. - Store prefix sum
0at index-1. - Set
prefix = 0. - Set
answer = 0. - Scan the array from left to right.
- Add the current number to
prefix. - Compute
need = prefix - k. - If
needexists infirst_index, update the answer. - If
prefixhas not appeared before, store its current index. - Return
answer.
Correctness
At index i, let prefix be the sum of nums[0] through nums[i].
A subarray ending at i has sum k exactly when there exists an earlier prefix sum need such that:
prefix - need = kThis is equivalent to:
need = prefix - kThe algorithm checks exactly this value in the dictionary.
If need was first seen at index j, then the subarray from j + 1 through i has sum k. Its length is:
i - jBecause the dictionary stores the earliest index for every prefix sum, this gives the longest valid subarray ending at i.
The algorithm checks every possible ending index i, so it considers every valid subarray. It keeps the maximum length found.
Therefore, the returned value is the maximum length of any subarray whose sum equals k.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan the array once |
| Space | O(n) | The dictionary may store up to n + 1 prefix sums |
Implementation
class Solution:
def maxSubArrayLen(self, nums: list[int], k: int) -> int:
first_index = {0: -1}
prefix = 0
answer = 0
for i, num in enumerate(nums):
prefix += num
need = prefix - k
if need in first_index:
answer = max(answer, i - first_index[need])
if prefix not in first_index:
first_index[prefix] = i
return answerCode Explanation
We initialize:
first_index = {0: -1}This handles subarrays starting at index 0.
For example, if nums[0..i] sums to k, then:
prefix - k = 0and the previous prefix index should be -1.
Then we scan the array:
for i, num in enumerate(nums):
prefix += numAt each index, compute the prefix sum needed to form k.
need = prefix - kIf need was seen before, the subarray after that earlier index sums to k.
if need in first_index:
answer = max(answer, i - first_index[need])Then store the current prefix sum only if it has not appeared before.
if prefix not in first_index:
first_index[prefix] = iThis preserves the earliest index and maximizes length.
Testing
def run_tests():
s = Solution()
assert s.maxSubArrayLen([1, -1, 5, -2, 3], 3) == 4
assert s.maxSubArrayLen([-2, -1, 2, 1], 1) == 2
assert s.maxSubArrayLen([1, 2, 3], 7) == 0
assert s.maxSubArrayLen([1, 2, 3], 6) == 3
assert s.maxSubArrayLen([0, 0, 0], 0) == 3
assert s.maxSubArrayLen([2, -2, 2, -2], 0) == 4
assert s.maxSubArrayLen([-1, 1, -1, 1], 0) == 4
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[1,-1,5,-2,3], 3 | Official example |
[-2,-1,2,1], 1 | Official example with negatives |
[1,2,3], 7 | No valid subarray |
[1,2,3], 6 | Whole array is answer |
[0,0,0], 0 | Many repeated prefix sums |
[2,-2,2,-2], 0 | Longest zero-sum subarray |
[-1,1,-1,1], 0 | Negative and positive cancellation |