Start learning free

LESSON 316 OF 414 · SLIDING WINDOW

Subarray of Given Sum in Rust: O(n) Time, O(1) Space Sliding Window

Practice O(n) Read the full explanation

Given an array of non-negative integers and a number target, find a continuous subarray whose sum is equal to target.

Return the start and end indices denoting this subarray, or -1 and -1 if there is none.

If there are multiple solutions, you can return any of them.


Example:

Input: nums = [1, 1, 5, 2, 1, 3, 10, 2, 1], target = 21
Output: [2, 6]
Explanation: 5 + 2 + 1 + 3 + 10 = 21

Note:

1. The input array contains only non-negative integers.

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


STEP 1 OF 8
    Ctrl + Enter to check

    RUST · SLIDING WINDOW

    Subarray Of Given Sum Ii in Rust, explained

    Problem overview

    The function subarray_sum receives a vector of non-negative integers, nums, and a target. It must return the start and end indices, as a vector [start, end], of a continuous subarray whose elements add up to target. Both indices are inclusive. When no subarray matches, the function returns [-1, -1]. If several subarrays match, any of them is accepted.

    In the example, nums = [1, 1, 5, 2, 1, 3, 10, 2, 1] and target = 21. The elements at indices 2 to 6 are 5, 2, 1, 3 and 10, which add up to 21, so the answer is [2, 6].

    The statement asks for O(n) time and O(1) extra space, so the code can't build a table of prefix sums.

    Approach: a window whose sum never has to be recomputed

    The brute-force way tries every start and every end and adds up each subarray. That is O(n^3), or O(n^2) with a running sum per start.

    The key fact is that all numbers are non-negative. Adding an element to a subarray never lowers its sum, and removing one never raises it. That gives a simple rule for a window [j, i] with running sum sum:

    • if sum is too small, extend the window on the right;
    • if sum is too big, shrink it on the left;
    • if sum equals target, return the window.

    Shrinking from the left is safe because a bigger sum can only be fixed by dropping elements. The right end i comes from the for loop and moves one step each time. The left end j only moves forward. Neither index goes back, so there are at most about 2n moves in total.

    On the example, the running sum grows 1, 2, 7, 9, 10, 13 and reaches 23 when the 10 at index 6 joins. That is above 21, so the window drops index 0 (sum 22) and then index 1 (sum 21), and stops, because the sum is no longer above the target. The window is now [2, 6] and its sum equals the target.

    Step by step

    1. Declare the window's left end and the sum as two mutable variables: j, a usize because it indexes nums, and sum, an i32 like the numbers it adds up. Both start at 0.
    2. Loop over every right end i in 0..nums.len(). Each iteration makes nums[i] the last element of the window.
    3. Add nums[i] to sum, since it joins the window.
    4. Shrink while the sum is too big. A while loop runs while j < i and sum > target. The condition j < i keeps nums[i] in the window: even if it is larger than the target alone, the window never becomes empty.
    5. Drop the leftmost element. Subtract nums[j] from sum.
    6. Move j forward with j += 1. Rust has no ++.
    7. Check for a match. After the while loop, if sum == target, return vec![j as i32, i as i32]. Both indices are usize, and the function returns Vec<i32>, so they are cast. The first match found is the one that ends earliest.
    8. Return vec![-1, -1] after the loop, as the final expression, with no return and no semicolon.

    The solution in Rust

    impl Solution {
        fn subarray_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
            let mut j: usize = 0;
            let mut sum: i32 = 0;
            for i in 0..nums.len() {
                sum += nums[i];
                while j < i && sum > target {
                    sum -= nums[j];
                    j += 1;
                }
                if sum == target {
                    return vec![j as i32, i as i32];
                }
            }
            vec![-1, -1]
        }
    }
    

    The Rust details worth a closer look:

    • Two kinds of return. The early return vec![...]; leaves the function from inside the loops. The last line, vec![-1, -1], has no semicolon, so it is the value of the function when the loop ends without a match.
    • The as casts. j as i32 converts a usize to an i32. Casts with as never panic and silently truncate if the value doesn't fit. For indices of a vector that fits in memory and an answer of this size, that is fine here.
    • Why j < i guards the subtraction. Without it, j could pass i and the window would be empty or backwards. Because j stays below i and i is a valid index, nums[j] is always valid too.
    • Integer overflow. The sum is an i32. The lessons build with optimizations, where overflow wraps silently: 2_000_000_000 + 2_000_000_000 gives -294967296. In a debug build the same addition panics. The lesson's tests keep the sums small. For large inputs, use an i64 for sum.

    Complexity

    Time: O(n). i goes through the vector once. j only increases and never passes i, so the inner while loop runs at most n times over the whole call. Each step is constant work.

    Space: O(1). The function keeps two indices and a sum, however long the vector is. The returned two-element vector is the answer and doesn't depend on the input size.

    Edge cases and common mistakes

    • No match. [1, 2, 3] with target 7 returns [-1, -1].
    • Empty vector. The loop doesn't run, so the result is [-1, -1], even with a target of 0.
    • A single element. [4] with target 4 returns [0, 0]. The subarray may have length 1.
    • A big element. In [7, 3] with target 3, the 7 alone exceeds the target, but the window may not be empty at that point. The next iteration adds the 3, drops the 7 and returns [1, 1].
    • Zeros. [5, 0] with target 0 returns [1, 1]: the 5 is dropped, and the lone 0 is a valid subarray. Zeros are the reason the rule is "non-negative" and not "positive". Because the check is sum > target and not >=, a window with equal sum stays and is returned.
    • Several answers. [0, 0, 4] with target 4 returns [0, 2]. [2, 2] would also be valid, and any of them is accepted.
    • Negative numbers. The method only works because every number is non-negative. With negatives, a sum that is too big could become right again by adding more elements, and this scan would miss answers.

    Testing

    • nums = [1, 1, 5, 2, 1, 3, 10, 2, 1], target = 21 gives [2, 6].
    • nums = [1, 2, 3, 4], target = 5 gives [1, 2].
    • nums = [1, 2, 3], target = 6 gives [0, 2].
    • nums = [7, 3], target = 3 gives [1, 1].
    • nums = [3], target = 0 gives [-1, -1].
    • nums = [], target = 0 gives [-1, -1].
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free