Start learning free

LESSON 287 OF 347 · SLIDING WINDOW

Longest Subarray With Sum At Most S in C#: 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

    C# · SLIDING WINDOW

    Longest Subarray With Sum At Most S in C#, explained

    Problem overview

    LongestSubarray(nums, S) receives an array of positive integers and a limit S. It must find the longest contiguous subarray whose sum is at most S and return its start and end indices as a two-element array.

    In the example, nums = [3, 2, 5, 2, 2, 1, 1, 3, 1, 2] and S = 11. The subarray from index 3 to index 8 is [2, 2, 1, 1, 3, 1] with a sum of 10, and no longer subarray stays within 11, so the answer is [3, 8]. The note sets the budget: O(n) time and O(1) extra space.

    Approach: slide a window that never exceeds S

    The brute-force method tries every start and every end and sums each range, which is O(n²) pairs at least. The faster idea uses the fact that all numbers are positive. Adding an element to a subarray always raises its sum, and removing one always lowers it. So for each possible start, the best end can be found by growing the window to the right until the next element would break the limit.

    The key observation is that the end never has to move back. If the window from first to last - 1 fits within S, then after dropping nums[first], the window starting at first + 1 and ending at last - 1 fits too, because it is smaller. The search for the next start can therefore continue from the same last. Both indices only move forward, so every element enters the window once and leaves once, which gives O(n) overall.

    This lesson keeps the window as nums[first..last-1] and the variable last is the next element to try, so it is one past the right end of the window.

    Step by step

    1. Prepare the answer. answer is an int[] of size 2. C# fills a new array with zeros, so it starts as [0, 0], a placeholder replaced once a valid subarray is found.
    2. Declare the state. maxLen is the longest length so far, sum is the window's sum, and last is the index of the next element to try. All three are ints starting at 0, declared in one statement.
    3. Loop over start positions. first runs from 0 up to nums.Length - 1. last is declared outside this loop on purpose: when first moves, the window keeps its right end.
    4. Grow the window. The inner while loop continues while last < nums.Length and sum + nums[last] <= S. The bounds check comes first, and && stops evaluating as soon as it is false, so nums[last] is never read past the end.
    5. Add the element: sum += nums[last].
    6. Record a new best. The window now covers first through last, so its length is last - first + 1. If that beats maxLen, update maxLen and set answer to new int[]{first, last}. The strict < keeps the leftmost subarray when two have equal length.
    7. Move on: last++.
    8. Slide the start. After the while loop, subtract nums[first] from sum, because the next iteration starts one position to the right.
    9. Return answer after the for loop.

    The solution in C#

    class Solution {
        public int[] LongestSubarray(int[] nums, int S) {
            int[] answer = new int[2];
            int maxLen = 0, sum = 0, last = 0;
            for (int 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 = new int[]{first, last};
                    }
                    last++;
                }
                sum -= nums[first];
            }
            return answer;
        }
    }
    

    Some C# details are worth a closer look:

    • Arrays are zero-filled. new int[2] holds {0, 0}. The array's length is fixed at 2, and answer = new int[]{first, last} replaces the whole array with a new one instead of changing its elements.
    • Length, not Count. Arrays report their size through the Length property. Count belongs to collections such as List<int>.
    • Short-circuit &&. The right-hand side runs only when the left-hand side is true. Reversing the two conditions would throw an IndexOutOfRangeException when last reaches nums.Length.
    • Several variables in one declaration. int maxLen = 0, sum = 0, last = 0; declares three separate ints, each with its own initial value.
    • A subtle case. If nums[first] alone is bigger than S, the window is empty and last equals first, yet sum -= nums[first] still runs, so sum becomes negative. The next iteration then lets last pass over that element again: the inner loop adds nums[last] back, which brings sum back in line with the window. The result is right, but the bookkeeping is delicate.

    Tracing the example

    For first = 0, the window grows over 3, 2 and 5 (sum 10), and the next 2 would make 12, so it stops with the best [0, 2]. Dropping the 3 leaves a sum of 7. For first = 1, adding 2 and 2 gives 11 and the best [1, 4], and the next element 1 would reach 12. For first = 2, the window extends over 1 and 1 to reach [2, 6]. For first = 3, it adds 3 and 1 and reaches [3, 8], with length 6. No later start does better, and the method returns [3, 8].

    Complexity

    Time: O(n). The inner while loop does not restart for each first, because last lives outside the for loop. In total last increases at most n times and first increases n times, so the number of steps is at most about 2n.

    Extra space: O(1). The code keeps a few integers and a two-element array.

    Edge cases and common mistakes

    • Equal lengths. For [2, 2, 2] with S = 4, both [0, 1] and [1, 2] have length 2. The strict < keeps [0, 1]. Writing <= would return the later one.
    • A limit that fits everything. With a large S, the window covers the entire array and the answer is [0, nums.Length - 1].
    • No valid subarray. If every element is larger than S, nothing is ever recorded and the method returns the placeholder [0, 0]. That is not a valid subarray, so it's worth knowing what the code does in that case.
    • Empty array. The loop never runs and the method returns [0, 0].
    • Declaring last inside the for loop. Resetting it to 0 on every start turns the algorithm back into the quadratic one.
    • Forgetting sum -= nums[first]. The window would keep a stale sum and shrink too early.
    • Negative numbers. The argument that the end never has to move back relies on positive values. With negatives, removing an element can raise the sum, so the reasoning no longer holds.

    Testing

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