Start learning free

LESSON 313 OF 414 · SLIDING WINDOW

Longest Subarray With Sum At Most S in Rust: Sliding Window, O(n)

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

    RUST · SLIDING WINDOW

    Longest Subarray With Sum At Most S in Rust, explained

    Problem overview

    The function longest_subarray receives a vector nums of positive integers and a limit s. It must find the longest contiguous subarray whose sum is at most s and return its first and last indices as a Vec<i32>.

    For nums = [3, 2, 5, 2, 2, 1, 1, 3, 1, 2] and s = 11 the answer is [3, 8]. The subarray nums[3..=8] is [2, 2, 1, 1, 3, 1], with sum 10 and length 6. Adding the next element, 2, would make 12, which is over the limit. The statement asks for O(n) time and O(1) extra space.

    Approach: a window that never moves backward

    The first lesson on this problem tries every start and extends each one to the right, which is O(n²). This lesson avoids the repeated work. All numbers are positive, so two facts hold:

    • If the window from first to last fits under s, then any window inside it fits too.
    • If the window from first cannot be extended to include last + 1, then no window that starts at first or earlier can reach that far. A later start is the only way to include more.

    So the right end last never needs to go back. For each start first, keep extending the window to the right while it still fits. Then move first one step: the element at first leaves the window, the sum drops, and the old right end is a good place to continue. Each element is added once and removed once, so the total work is linear.

    In this lesson the window is described as the elements from first up to last - 1. The index last is the next element to try.

    Step by step

    1. Create answer as vec![0i32, 0i32]. It is a placeholder for the start and end indices, replaced as soon as a valid subarray is found. The suffixes make the elements i32, matching the return type.
    2. Create three more variables, each with let mut: max_len as 0i32 (the longest length so far), sum as 0i32 (the window's sum) and last as 0usize (the next element to try). last is a usize because it indexes nums.
    3. Loop over each start: for first in 0..n. The variable last is declared outside this loop on purpose. It keeps its value when first moves, which is what makes the scan linear.
    4. Grow the window with while last < n && sum + nums[last] <= s. The bound check comes first, so nums[last] is never read past the end.
    5. Add the element: sum += nums[last].
    6. Record the length. The window now covers first through last, so its length is last - first + 1. If that is more than max_len, update both max_len and answer. The indices are usize and max_len is i32, so each is converted with as i32. Using < rather than <= keeps the leftmost window when two have the same length.
    7. Move on: last += 1. Rust has no ++ operator.
    8. Slide the start. After the while loop, sum -= nums[first]: the element at first leaves the window before first advances.
    9. Return answer after the loop, with no semicolon.

    The solution in Rust

    impl Solution {
        fn longest_subarray(nums: Vec<i32>, s: i32) -> Vec<i32> {
            let n = nums.len();
            let mut answer = vec![0i32, 0i32];
            let mut max_len = 0i32;
            let mut sum = 0i32;
            let mut last = 0usize;
            for first in 0..n {
                while last < n && sum + nums[last] <= s {
                    sum += nums[last];
                    if max_len < (last - first + 1) as i32 {
                        max_len = (last - first + 1) as i32;
                        answer = vec![first as i32, last as i32];
                    }
                    last += 1;
                }
                sum -= nums[first];
            }
            answer
        }
    }
    

    Rust details worth knowing:

    • No implicit conversions. Rust never mixes integer types silently. last - first + 1 is a usize, so comparing it with the i32 max_len needs as i32. The cast is safe here because lengths are small.
    • && short-circuits. If last < n is false, the right side is not evaluated, so nums[last] never runs with last == n. Indexing out of bounds would panic.
    • The final expression. answer without a semicolon is the function's value. With a semicolon the body would evaluate to () and not compile.
    • Subtraction on usize. The lessons build with optimizations, where usize subtraction that goes below zero wraps silently. In a debug build, the same overflow panics. This matters in one situation, described below.

    Complexity

    Time: O(n). The index last only increases, and it can increase at most n times over the whole run. The outer loop runs n times. The inner while loop, summed over all outer passes, runs at most n times, so the two together are linear.

    Space: O(1). The code keeps a few integers and the two-element answer.

    Edge cases and common mistakes

    • Everything fits. [1, 2, 3] with s = 100 returns [0, 2], the whole vector.
    • Ties. [1, 1, 1] with s = 2 returns [0, 1], because the strict comparison keeps the first window of that length.
    • A single element over the limit. In [5, 1, 1, 9, 2] with s = 3, the 5 and the 9 can never be part of a window, and the answer is [1, 2]. When the window is empty at some first, last still equals first, and the code subtracts an element that was never added. The sum goes negative, and the next pass re-adds the same element through last, which restores balance. In an optimized build this gives correct answers. In a debug build, the length computation last - first + 1 at that moment underflows and panics.
    • Nothing qualifies. If every element exceeds s, as in [9] with s = 3, no subarray is valid, but the function returns the placeholder [0, 0]. The lesson does not define another result for this case.
    • Negative numbers. The window idea depends on positive values. With negatives, dropping an element can raise the sum, so the pointer logic breaks.
    • Resetting last inside the loop. If last is set back to first on each pass, the algorithm becomes quadratic again.
    • Forgetting to subtract nums[first]. The sum then keeps elements that are no longer in the window, and later windows come out too short.

    Testing

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