Longest Subarray with sum K (Positives)
Description
Given an array arr[] of positive integers and an integer k, find the length of the longest contiguous subarray whose elements sum to exactly k.
A subarray is a contiguous (non-empty) portion of the array — it consists of consecutive elements from arr. You need to find the subarray with sum equal to k that has the maximum possible length. If no subarray sums to k, return 0.
Important: All elements in the array are positive (greater than 0). This property is crucial because it means that as you extend a subarray by including more elements, the sum strictly increases — and as you shrink it, the sum strictly decreases. This monotonic behavior enables efficient solutions.
Examples
Example 1
Input: arr = [1, 2, 3, 1, 1, 1, 1], k = 3
Output: 3
Explanation: There are multiple subarrays that sum to 3:
- [1, 2] (indices 0-1, length 2)
- [3] (index 2, length 1)
- [1, 1, 1] (indices 3-5, length 3)
The longest among them is [1, 1, 1] with length 3.
Example 2
Input: arr = [2, 3, 5, 1, 9], k = 10
Output: 3
Explanation: Subarrays summing to 10:
- [2, 3, 5] (indices 0-2, length 3)
- [1, 9] (indices 3-4, length 2)
The longest is [2, 3, 5] with length 3.
Example 3
Input: arr = [1, 1, 1, 1, 1], k = 100
Output: 0
Explanation: The total sum of the entire array is only 5, which is far less than 100. No subarray can possibly sum to 100, so the answer is 0.
Constraints
- 1 ≤ arr.length ≤ 10^5
- 1 ≤ arr[i] ≤ 10^4
- 1 ≤ k ≤ 10^9
Editorial
Brute Force
Intuition
The most straightforward approach is to examine every possible subarray, compute its sum, and check if it equals k. If it does, compare its length against the longest we've found so far.
A subarray is defined by its starting index i and ending index j (where i ≤ j). So we enumerate all possible (i, j) pairs and calculate the sum of elements from index i to j.
Imagine you have a row of coins with different values. To find the longest consecutive group of coins summing to a target amount, you'd pick every possible starting coin, then keep adding coins to the right until you either hit the target, exceed it, or run out of coins. You'd track the longest group that matched exactly.
Step-by-Step Explanation
Let's trace with arr = [1, 2, 3, 1, 1, 1, 1], k = 3:
Step 1: Fix i=0. Try all subarrays starting at index 0:
- j=0: sum = 1. Not equal to 3.
- j=1: sum = 1+2 = 3. Match! Length = 2. Update max_len = 2.
- j=2: sum = 1+2+3 = 6. Exceeds 3. Since all elements are positive, extending further only increases the sum. But we still check to be thorough.
- j=3: sum=7, j=4: sum=8, j=5: sum=9, j=6: sum=10. None match.
Step 2: Fix i=1. Try subarrays starting at index 1:
- j=1: sum=2. No match.
- j=2: sum=2+3=5. No match.
- j=3: sum=2+3+1=6. No match. Continue through remaining, none match.
Step 3: Fix i=2. j=2: sum=3. Match! Length = 1. max_len stays 2 (already better).
Step 4: Fix i=3. j=3: sum=1, j=4: sum=2, j=5: sum=1+1+1=3. Match! Length = 3. Update max_len = 3.
Step 5: Fix i=4. j=4: sum=1, j=5: sum=2, j=6: sum=3. Match! Length = 3. max_len stays 3 (tied).
Step 6: Fix i=5. j=5: sum=1, j=6: sum=2. No match. Fix i=6. j=6: sum=1. No match.
Result: max_len = 3.
Brute Force — Enumerate All Subarrays — For each starting index i, extend j rightward and accumulate the sum. When sum equals k, record the subarray length. Track the longest match found.
Algorithm
- Initialize
max_len = 0 - For each starting index
ifrom 0 to n-1:
a. Initializesum = 0
b. For each ending indexjfromito n-1:- Add
arr[j]tosum - If
sum == k, updatemax_len = max(max_len, j - i + 1) - If
sum > k, break (since all elements are positive, extending further won't help)
- Add
- Return
max_len
Code
#include <vector>
#include <algorithm>
using namespace std;
int longestSubarrayWithSumK(vector<int>& arr, int k) {
int n = arr.size();
int maxLen = 0;
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += arr[j];
if (sum == k) {
maxLen = max(maxLen, j - i + 1);
}
if (sum > k) break; // Optimization for positive integers
}
}
return maxLen;
}def longest_subarray_with_sum_k(arr, k):
n = len(arr)
max_len = 0
for i in range(n):
current_sum = 0
for j in range(i, n):
current_sum += arr[j]
if current_sum == k:
max_len = max(max_len, j - i + 1)
if current_sum > k:
break # Optimization for positive integers
return max_lenclass Solution {
public int longestSubarrayWithSumK(int[] arr, int k) {
int n = arr.length;
int maxLen = 0;
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += arr[j];
if (sum == k) {
maxLen = Math.max(maxLen, j - i + 1);
}
if (sum > k) break;
}
}
return maxLen;
}
}Complexity Analysis
Time Complexity: O(n²) in the worst case
The outer loop runs n times. For each starting index i, the inner loop can run up to n-i times. With the early break optimization (since elements are positive, we stop when sum > k), the average case can be better. But in the worst case — for example, when k is very large and no early break triggers — the total work is n + (n-1) + ... + 1 = n(n+1)/2 = O(n²).
Space Complexity: O(1)
We only use a few integer variables. No additional data structures that scale with input size.
Why This Approach Is Not Efficient
With n up to 10^5, the brute force approach can require up to ~5×10^9 operations in the worst case — far too slow for typical time limits.
The fundamental inefficiency is that we restart the sum computation from scratch for every new starting index i. When we move from i to i+1, we discard all the accumulated knowledge from the previous inner loop and start summing all over again.
There are two key observations that can help:
-
Prefix Sum + Hash Map: If we precompute prefix sums, then the sum of any subarray arr[i..j] equals prefix[j+1] - prefix[i]. Finding whether any previous prefix sum equals
current_prefix - kbecomes a hash map lookup in O(1). This works for arrays with positive, negative, or zero values. -
Sliding Window / Two Pointers: Since all elements are positive, subarray sums have a monotonic property — adding an element always increases the sum, and removing one always decreases it. This means we can use two pointers that only move forward, achieving O(n) time.
Let's explore both approaches.
Better Approach - Prefix Sum with Hash Map
Intuition
The prefix sum technique transforms the problem of computing subarray sums into simple subtraction.
Define prefix[i] as the sum of the first i elements: prefix[0] = 0, prefix[1] = arr[0], prefix[2] = arr[0] + arr[1], and so on. Then the sum of any subarray arr[i..j] is simply prefix[j+1] - prefix[i].
Now the question becomes: for each index j, does there exist an earlier index i such that prefix[j+1] - prefix[i] = k? Rearranging: prefix[i] = prefix[j+1] - k.
So at each position, we compute the current prefix sum and ask: "Have I seen the value current_prefix - k before?" This is a classic hash map lookup.
To find the longest subarray, we store the earliest index where each prefix sum value was first seen. If the same prefix sum appears later, we don't overwrite — keeping the earliest occurrence maximizes the subarray length.
Think of it like a GPS tracking your cumulative distance walked. If at mile 15 you realize you need a stretch of exactly 3 miles, you ask: "When was I at mile 12?" If you crossed mile 12 at checkpoint #4, then the stretch from checkpoint #4 to now is exactly 3 miles long.
Step-by-Step Explanation
Let's trace with arr = [1, 2, 3, 1, 1, 1, 1], k = 3:
Step 1: Initialize hash map with {0: -1} (prefix sum 0 occurs "before" index 0). prefix_sum = 0, max_len = 0.
Step 2: Process index 0: prefix_sum = 0 + 1 = 1. Need = 1 - 3 = -2. Is -2 in map? No. Store {1: 0}. Map: {0:-1, 1:0}.
Step 3: Process index 1: prefix_sum = 1 + 2 = 3. Need = 3 - 3 = 0. Is 0 in map? YES, at index -1. Length = 1 - (-1) = 2. Update max_len = 2. Store {3: 1}.
Step 4: Process index 2: prefix_sum = 3 + 3 = 6. Need = 6 - 3 = 3. Is 3 in map? YES, at index 1. Length = 2 - 1 = 1. max_len stays 2. Store {6: 2}.
Step 5: Process index 3: prefix_sum = 6 + 1 = 7. Need = 7 - 3 = 4. Is 4 in map? No. Store {7: 3}.
Step 6: Process index 4: prefix_sum = 7 + 1 = 8. Need = 8 - 3 = 5. Is 5 in map? No. Store {8: 4}.
Step 7: Process index 5: prefix_sum = 8 + 1 = 9. Need = 9 - 3 = 6. Is 6 in map? YES, at index 2. Length = 5 - 2 = 3. Update max_len = 3.
Step 8: Process index 6: prefix_sum = 9 + 1 = 10. Need = 10 - 3 = 7. Is 7 in map? YES, at index 3. Length = 6 - 3 = 3. max_len stays 3.
Result: max_len = 3.
Prefix Sum + Hash Map — Find Earliest Complement — Build the prefix sum as we scan. At each position, look up whether (prefix_sum - k) appeared earlier in the hash map. The earliest occurrence gives the longest subarray.
Algorithm
- Initialize a hash map with
{0: -1}(prefix sum 0 is seen before the array starts) - Initialize
prefix_sum = 0,max_len = 0 - For each index
ifrom 0 to n-1:
a. Addarr[i]toprefix_sum
b. Computeneed = prefix_sum - k
c. Ifneedexists in the hash map at indexj, thenlength = i - j. Updatemax_len = max(max_len, length)
d. Ifprefix_sumis NOT already in the hash map, store{prefix_sum: i}(keep earliest occurrence only) - Return
max_len
Code
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
int longestSubarrayWithSumK(vector<int>& arr, int k) {
int n = arr.size();
unordered_map<long long, int> prefixMap;
prefixMap[0] = -1; // Prefix sum 0 before array starts
long long prefixSum = 0;
int maxLen = 0;
for (int i = 0; i < n; i++) {
prefixSum += arr[i];
long long need = prefixSum - k;
if (prefixMap.count(need)) {
maxLen = max(maxLen, i - prefixMap[need]);
}
// Only store the first occurrence of each prefix sum
if (!prefixMap.count(prefixSum)) {
prefixMap[prefixSum] = i;
}
}
return maxLen;
}def longest_subarray_with_sum_k(arr, k):
prefix_map = {0: -1} # Prefix sum 0 before array starts
prefix_sum = 0
max_len = 0
for i, num in enumerate(arr):
prefix_sum += num
need = prefix_sum - k
if need in prefix_map:
max_len = max(max_len, i - prefix_map[need])
# Only store the first occurrence of each prefix sum
if prefix_sum not in prefix_map:
prefix_map[prefix_sum] = i
return max_lenimport java.util.HashMap;
class Solution {
public int longestSubarrayWithSumK(int[] arr, int k) {
HashMap<Long, Integer> prefixMap = new HashMap<>();
prefixMap.put(0L, -1); // Prefix sum 0 before array starts
long prefixSum = 0;
int maxLen = 0;
for (int i = 0; i < arr.length; i++) {
prefixSum += arr[i];
long need = prefixSum - k;
if (prefixMap.containsKey(need)) {
maxLen = Math.max(maxLen, i - prefixMap.get(need));
}
// Only store the first occurrence of each prefix sum
if (!prefixMap.containsKey(prefixSum)) {
prefixMap.put(prefixSum, i);
}
}
return maxLen;
}
}Complexity Analysis
Time Complexity: O(n)
We traverse the array once. Each iteration involves computing the prefix sum (O(1)), a hash map lookup (O(1) average), and a conditional hash map insertion (O(1) average). Total: n × O(1) = O(n).
Space Complexity: O(n)
In the worst case, every prefix sum is unique, so the hash map stores n+1 entries. This requires O(n) additional space.
Why This Approach Is Not Efficient
The prefix sum + hash map approach is O(n) in time, which is asymptotically optimal. However, it uses O(n) space for the hash map. For this specific problem where all elements are positive, we can do better.
The key observation: since every element is positive (≥ 1), adding an element to a subarray always increases its sum, and removing an element always decreases it. This means subarray sums are monotonically increasing as we extend and monotonically decreasing as we shrink.
This monotonic property means we never need to "jump back" — we can use a sliding window (two pointers) that only moves forward. If the current window sum is too small, we expand the right end. If it's too large, we shrink the left end. This achieves O(n) time with O(1) space.
Note: this optimization is ONLY valid when all elements are positive. If the array could contain zeros or negatives, the sum wouldn't be monotonically increasing with window size, and the hash map approach would be necessary.
Optimal Approach - Sliding Window (Two Pointers)
Intuition
Since all elements are positive, we can maintain a sliding window defined by two pointers left and right, both starting at 0. We track the sum of elements inside the window.
- Expand: Move
rightforward to include a new element, increasing the window sum. - Shrink: Move
leftforward to exclude the leftmost element, decreasing the window sum.
The strategy:
- Keep expanding
rightto grow the window. - If the window sum exceeds
k, shrink from the left until the sum is ≤ k. - If the window sum equals
kexactly, record the window length if it's a new maximum.
Why does this work? Because all values are positive:
- When
sum > k: no matter how far we extendright, the sum will only get larger. The only way to reduce it is to moveleftforward. So we shrink. - When
sum < k: we haven't accumulated enough yet, so extendright. - When
sum == k: we've found a valid subarray. Record it and keep going to see if a longer one exists.
Both pointers only move forward (never backward), so each element is added at most once and removed at most once. This gives exactly 2n pointer movements = O(n) time.
Think of a rubber band stretched between your two hands over a ruler. You slide both hands rightward along the ruler, but never leftward. Your right hand moves to stretch the band (increase sum), and your left hand catches up to release tension (decrease sum). The longest stretch where the band tension equals exactly k is your answer.
Step-by-Step Explanation
Let's trace with arr = [1, 2, 3, 1, 1, 1, 1], k = 3:
Step 1: Initialize left=0, right=0, window_sum=0, max_len=0.
Step 2: Expand right=0: window_sum = 0+1 = 1. sum < k. Window: [1].
Step 3: Expand right=1: window_sum = 1+2 = 3. sum == k! Length = 1-0+1 = 2. Update max_len = 2. Window: [1, 2].
Step 4: Expand right=2: window_sum = 3+3 = 6. sum > k. Need to shrink.
Step 5: Shrink left=0→1: window_sum = 6-1 = 5. Still > k. Shrink again.
Step 6: Shrink left=1→2: window_sum = 5-2 = 3. sum == k! Length = 2-2+1 = 1. max_len stays 2. Window: [3].
Step 7: Expand right=3: window_sum = 3+1 = 4. sum > k. Shrink left=2→3: window_sum = 4-3 = 1. sum < k. Window: [1].
Step 8: Expand right=4: window_sum = 1+1 = 2. sum < k. Window: [1, 1].
Step 9: Expand right=5: window_sum = 2+1 = 3. sum == k! Length = 5-3+1 = 3. Update max_len = 3. Window: [1, 1, 1].
Step 10: Expand right=6: window_sum = 3+1 = 4. sum > k. Shrink left=3→4: window_sum = 4-1 = 3. sum == k! Length = 6-4+1 = 3. max_len stays 3. Window: [1, 1, 1].
Result: max_len = 3.
Sliding Window — Expand Right, Shrink Left — Watch the two pointers slide forward through the array. The window expands when the sum is too small, and shrinks when the sum is too large. When the sum exactly equals k, we record the window length.
Algorithm
- Initialize
left = 0,window_sum = 0,max_len = 0 - For
rightfrom 0 to n-1 (expand window):
a. Addarr[right]towindow_sum
b. Whilewindow_sum > k(window too large):- Subtract
arr[left]fromwindow_sum - Increment
left
c. Ifwindow_sum == k: - Update
max_len = max(max_len, right - left + 1)
- Subtract
- Return
max_len
Code
#include <vector>
#include <algorithm>
using namespace std;
int longestSubarrayWithSumK(vector<int>& arr, int k) {
int n = arr.size();
int left = 0;
long long windowSum = 0;
int maxLen = 0;
for (int right = 0; right < n; right++) {
windowSum += arr[right];
// Shrink window from left while sum exceeds k
while (windowSum > k) {
windowSum -= arr[left];
left++;
}
// Check if current window sums to exactly k
if (windowSum == k) {
maxLen = max(maxLen, right - left + 1);
}
}
return maxLen;
}def longest_subarray_with_sum_k(arr, k):
n = len(arr)
left = 0
window_sum = 0
max_len = 0
for right in range(n):
window_sum += arr[right]
# Shrink window from left while sum exceeds k
while window_sum > k:
window_sum -= arr[left]
left += 1
# Check if current window sums to exactly k
if window_sum == k:
max_len = max(max_len, right - left + 1)
return max_lenclass Solution {
public int longestSubarrayWithSumK(int[] arr, int k) {
int n = arr.length;
int left = 0;
long windowSum = 0;
int maxLen = 0;
for (int right = 0; right < n; right++) {
windowSum += arr[right];
// Shrink window from left while sum exceeds k
while (windowSum > k) {
windowSum -= arr[left];
left++;
}
// Check if current window sums to exactly k
if (windowSum == k) {
maxLen = Math.max(maxLen, right - left + 1);
}
}
return maxLen;
}
}Complexity Analysis
Time Complexity: O(n)
Although there's a while loop inside the for loop, the total work across all iterations is bounded. The right pointer moves from 0 to n-1 (n moves). The left pointer also only moves forward and can move at most n times total (it never moves backward). So the total number of pointer moves is at most 2n = O(n).
Each element is added to window_sum exactly once (when right passes over it) and removed at most once (when left passes over it). This amortized analysis confirms O(n) total work.
Space Complexity: O(1)
We only use three variables (left, window_sum, max_len) plus the loop variable right. No hash map, no prefix array — just constant extra space. This is an improvement over the prefix sum approach's O(n) space.
Why this is optimal: We must examine each element at least once → Ω(n) is a lower bound. The sliding window achieves O(n) time and O(1) space, matching the lower bound for both. No algorithm can do better.