Problem overview
The method longestSubarray receives an array nums of positive integers and a number S. It must find the longest contiguous subarray whose sum is at most S, and return its first and last index as [start, end].
In the example, nums = [3, 2, 5, 2, 2, 1, 1, 3, 1, 2] and S = 11. The subarray at indices 3 to 8 is 2, 2, 1, 1, 3, 1. It has six elements and a sum of 10, which is within the limit. No window of seven elements fits under 11, so the answer is [3, 8].
The statement allows O(n²) time and O(1) extra space, which is what a double loop over starts and ends costs.
Approach: every start, every end, one running sum
A subarray is determined by where it starts (i) and where it ends (j). The plan is to try every start. For each start, extend the end one element at a time and keep a running sum. After each extension, check two things: the sum is still at most S, and the subarray is longer than the best one found so far.
The running sum is what keeps the cost at O(n²). Extending the end by one element needs a single addition, because the new sum is the old sum plus nums[j]. Recomputing the sum of nums[i...j] from scratch for every pair would add another loop and make it O(n³).
Take [3, 1, 2] with S = 3. Starting at index 0, the sums are 3 (valid, length 1), 4 (too big) and 6 (too big). Starting at index 1, the sums are 1 (valid, but length 1 does not beat 1), 3 (valid, length 2, a new best, so the answer becomes [1, 2]). Starting at index 2, the sum is 2 (length 1, not better). The final answer is [1, 2].
A faster solution exists, and it relies on the numbers being positive; this lesson stays with the simple double loop.
Step by step
- Create
answeras an empty[Int](). It will hold the two indices of the best subarray found so far. Each time a longer one is found, it is replaced. - Create
maxLenat 0. It is the length of the best subarray found so far. With 0, the first valid subarray of any length is an improvement. - Loop over the start
ifrom 0 up to but not includingnums.count. - Create
sumat 0 inside this loop. It is the sum ofnums[i...j], and it has to start over for every newi. Declaring it inside the outer loop does that automatically. - Loop over the end
jfromito the last index. Beginning atimakes the one-element subarraynums[i]the first one tried. - Add
nums[j]tosum. Nowsumis the sum of the current subarray. - Check both conditions with
&&: the sum is at mostS, andmaxLenis less than the current length,j - i + 1. The length is the number of indices fromitojinclusive. - Update
maxLento the new length. - Set
answerto[i, j]. - Return
answerafter both loops.
The solution in Swift
class Solution {
func longestSubarray(_ nums: [Int], _ S: Int) -> [Int] {
var answer = [Int]()
var maxLen = 0
for i in 0..<nums.count {
var sum = 0
for j in i..<nums.count {
sum += nums[j]
if sum <= S && maxLen < (j - i + 1) {
maxLen = j - i + 1
answer = [i, j]
}
}
}
return answer
}
}
The Swift details worth knowing:
- Half-open ranges.
0..<nums.countstops beforecount, so the last index used iscount - 1.i..<nums.countis never a backwards range, becauseiis at mostcount - 1. - A capital parameter name. The parameter is called
S. Swift is case-sensitive, andSis an ordinaryIntparameter here. &&short-circuits. Ifsum <= Sis false, the length comparison is not evaluated. Both sides are cheap, so this only matters for how you read the condition.varfor state that changes.answer,maxLenandsumall change after they are created, so none of them can be alet.- Arrays are values.
answer = [i, j]replaces the whole array with a new one; nothing else refers to the old one.
Complexity
Time: O(n²). For each of the n starts, the inner loop visits up to n - i ends and does a constant amount of work for each, so the total is about n² / 2 steps.
Space: O(1). The method keeps a few integers and the two-element answer.
Because every number is positive, the sum only grows as j moves right. Once it passes S, no longer subarray from the same start can fit, so you could stop the inner loop there with break. That saves work, but the worst case is still O(n²). For example, when S is very large, every pair is visited anyway.
Edge cases and common mistakes
- No valid subarray. If every element is larger than
S, nothing ever passes the check. For[5, 6, 7]withS = 4, the method returns an empty array,[]. The caller should be ready for that. - The whole array fits.
[1, 2, 3]withS = 100returns[0, 2]. - Ties. Because the comparison is strict (
maxLen < ...), the first of several equally long subarrays is kept. For[2, 2, 2]withS = 4the result is[0, 1]. - Sum exactly equal to
S. "At most" includes equality. For[4, 1, 1, 4]withS = 2, the subarray[1, 1]sums to 2 and is valid, so the answer is[1, 2]. - Using
<instead of<=for the sum. That would reject subarrays whose sum equalsS. - Length off by one. The length of
nums[i...j]isj - i + 1. Usingj - iundercounts by one. - Resetting
sumin the wrong place. Declaring it outside the outer loop would carry the old total into the next start.
Testing
nums = [3, 2, 5, 2, 2, 1, 1, 3, 1, 2],S = 11gives[3, 8].nums = [5, 6, 7],S = 4gives[].nums = [1, 2, 3],S = 100gives[0, 2].nums = [4, 1, 1, 4],S = 2gives[1, 2].nums = [3, 1, 2],S = 3gives[1, 2].
