Start learning free

LESSON 288 OF 348 · SLIDING WINDOW

Longest Subarray with Sum at Most S in TypeScript: O(n) Sliding Window

Practice O(n) Read the full explanation

Given an array of positive integers and a number S, find the longest contiguous subarray having the sum at most S.

Return the start and end indices denoting this subarray.

Example

Input: nums = [3, 2, 5, 2, 2, 1, 1, 3, 1 , 2], S = 11
Output: [3, 8]
Explanation:the subarray nums[3...8] of sum 10

Note:

Your algorithm should run in O(n) time and use O(1) extra space.


STEP 1 OF 9
    Ctrl + Enter to check

    TYPESCRIPT · SLIDING WINDOW

    Longest Subarray With Sum At Most S in TypeScript, explained

    Problem overview

    The method longestSubarray receives an array nums of positive integers and a limit S. It must find the longest contiguous run of elements whose sum is at most S, and return its first and last indices as [start, end]. The statement asks for O(n) time and O(1) extra space.

    In the example, nums = [3, 2, 5, 2, 2, 1, 1, 3, 1, 2] and S = 11. The answer is [3, 8]: the elements at indices 3 to 8 are 2, 2, 1, 1, 3, 1, which sum to 10 and make a window of length 6. Adding the next element, 2, would give 12, which is over the limit. No run of 7 elements fits within 11.

    Approach: a window that only moves right

    The brute-force way tries every start index and extends the run to the right, recomputing sums as it goes. That is O(n²). The faster way uses a fact about positive numbers: adding an element always makes a sum larger, and removing one always makes it smaller.

    Keep a window of consecutive elements from index first up to, but not including, last, plus the running sum of that window. For each start first:

    • Push last forward while the next element still fits, that is, while sum + nums[last] <= S.
    • The window is now as long as it can be for this start. Record it if it beats the best so far.
    • Move the start forward: subtract nums[first] from sum.

    The key point is that last is never reset. If the window from first reached last, then from first + 1 it reaches at least as far, since the window only lost an element and the sum can only drop. Every element enters the window once and leaves it once, so the total work is linear.

    On the example, the window starting at 0 takes 3, 2, 5 (sum 10) and stops, because the next 2 would make 12. It starts at index 1 with sum 7 and takes 2, 2 (sum 11, indices 1 to 4), then stops. The best so far is length 4. Later starts reach [3, 8] with length 6, which no later start beats.

    Step by step

    1. Declare answer as a number[] holding [0, 0]. This placeholder is replaced the first time a valid window is found.
    2. Declare maxLen, sum and last in one let, all 0. They hold the best length so far, the window's sum, and the index of the next element to try.
    3. Loop over each start index first. last is declared outside this loop on purpose, so it keeps its place when first moves.
    4. Grow the window with a while loop while last < nums.length && sum + nums[last] <= S. The bounds check comes first, and && stops evaluating when it fails, so nums[last] is never read past the end.
    5. Add nums[last] to sum to put the element in the window.
    6. Record the length if it is a new best. The window covers first through last, so its length is last - first + 1. When maxLen is smaller, update maxLen and set answer to [first, last]. Using < rather than <= keeps the leftmost window when two have the same length.
    7. Increment last to try the next element.
    8. After the while loop, subtract nums[first] from sum, since the window is about to slide one step to the right.
    9. Return answer after the for loop.

    The solution in TypeScript

    class Solution {
        longestSubarray(nums: number[], S: number): number[] {
            let answer: number[] = [0, 0];
            let maxLen = 0, sum = 0, last = 0;
            for (let first = 0; first < nums.length; first++) {
                while (last < nums.length && sum + nums[last] <= S) {
                    sum += nums[last];
                    if (maxLen < last - first + 1) {
                        maxLen = last - first + 1;
                        answer = [first, last];
                    }
                    last++;
                }
                sum -= nums[first];
            }
            return answer;
        }
    }
    

    The TypeScript details worth knowing:

    • Several variables in one let. let maxLen = 0, sum = 0, last = 0; declares three variables. Each is inferred as a number.
    • Return type. number[] is an array of numbers. The method returns a new two-element array each time it improves, replacing answer instead of changing it.
    • Reading past the end. TypeScript does not stop nums[nums.length]: at run time it gives undefined, and sum + undefined is NaN. A comparison with NaN is always false, so the loop would stop by accident. The explicit last < nums.length test keeps the code correct on purpose.
    • Numbers and precision. Sums stay exact as long as they stay below 2^53 (about 9 * 10^15), which is far above any realistic input here.
    • A window that is momentarily empty. If nums[first] alone is larger than S, the while loop adds nothing, and last may sit behind first. The code subtracts nums[first] anyway, so sum is temporarily negative. It still equals the sum of the elements from first up to last counted with a sign, and when last catches up, the arithmetic comes out right. For nums = [5, 1] and S = 3, the code returns [1, 1].

    Complexity

    Time: O(n). first goes from 0 to n - 1 once, and last only moves forward, from 0 to at most n. Across the whole run, the while body executes at most n times, so the total is about 2n steps.

    Space: O(1). The code keeps a few numbers and one two-element array. It doesn't create a prefix-sum array or copy any part of the input.

    Edge cases and common mistakes

    • The whole array fits. [1, 2, 3] with S = 100 gives [0, 2]. The window grows once and never shrinks.
    • Ties. [2, 2, 2] with S = 4 has two windows of length 2. Because of the strict <, the leftmost wins: [0, 1].
    • Single elements bigger than S. In [9, 9, 1, 1, 9] with S = 2, the answer is [2, 3]. The large values can't be in any window.
    • No valid subarray. For [5] with S = 3, nothing fits, and the method returns the placeholder [0, 0], which looks like a window of one element. The code has no separate way to say "no window".
    • An empty array. The loop never runs, and the placeholder is returned.
    • Resetting last or sum for every start. That throws away the saved work and turns the algorithm back into O(n²).
    • Negative numbers. The method relies on positive values. With negatives, a longer window could have a smaller sum, so moving last only forward misses valid windows.
    • Using <= in the length check. It returns the last of several equal-length windows instead of the first.

    Testing

    • nums = [3, 2, 5, 2, 2, 1, 1, 3, 1, 2], S = 11 gives [3, 8].
    • nums = [1, 2, 3], S = 100 gives [0, 2].
    • nums = [2, 2, 2], S = 4 gives [0, 1].
    • nums = [9, 9, 1, 1, 9], S = 2 gives [2, 3].
    • nums = [5, 1], S = 3 gives [1, 1].
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free