Start learning free

LESSON 243 OF 415 · PARTIAL SUMS #2

Remove Min Sum II in Swift: Sliding Window in O(n)

Practice O(n) Read the full explanation

Given an input array of integers and a number k, pick exactly k numbers, some from the beginning of the array and some from the end of it, in such a way that their sum is minimized. Return this minimum sum.

Example

Input: nums = [-2, 5, 2, -1, 3, -10, 9, -2], k = 4
Output: -5
Explanation: Pick the first and the last three numbers
             (-2) + (-10) + 9 + (-2) = -5
STEP 1 OF 11
    Ctrl + Enter to check

    SWIFT · PARTIAL SUMS #2

    Remove Min Sum Ii in Swift, explained

    Problem overview

    The method removeMinSum receives an array of integers, nums, and a number k. You must pick exactly k numbers, some from the beginning of the array and some from the end, and the method returns the smallest sum you can get this way. Picking all k numbers from one end, or none from one end, is allowed.

    In the example, nums = [-2, 5, 2, -1, 3, -10, 9, -2] and k = 4. Taking the first number and the last three gives (-2) + (-10) + 9 + (-2) = -5, and no other split of four numbers does better.

    Approach: keep the best block in the middle

    The direct way is to try every split: take a numbers from the front and k - a from the back, for a from 0 to k, and add them up. That works, and a prefix-sum table makes each split cost O(1).

    This lesson uses a different view. Whatever you pick from the two ends, the numbers you do not pick form one contiguous block in the middle, and that block always has exactly n - k numbers. The picked sum and the block sum add up to the total of the array, so:

    picked = total - block

    To make picked as small as possible, make block as large as possible. The task becomes: find the contiguous window of n - k numbers with the maximum sum, then subtract that sum from the total.

    In the example the total is 4 and the windows of four numbers have sums 4, 9, -6, 1 and 0. The best window is [5, 2, -1, 3] with sum 9, and the answer is 4 - 9 = -5. This matches the picked numbers in the statement: the first number, -2, and the last three.

    A window sum doesn't need to be recomputed from scratch. When the window moves one position to the right, one number enters and one leaves, so the sum changes by two additions. That is the sliding window technique, and it makes the whole algorithm one pass over the array.

    Step by step

    1. Compute the window length. window is nums.count - k. It is a let, because it never changes. The name must be new: a Swift parameter such as k is a constant and can't be reassigned.
    2. Keep the current window's sum in rollingSum, a var starting at 0.
    3. Keep the sum of the whole array in totalSum, also starting at 0. Adding to it inside the main loop saves a second pass.
    4. Track the best window sum in maxSum. It starts at Int.min, the smallest possible Int, so that the first full window replaces it. Starting at 0 would give wrong answers when every window has a negative sum.
    5. Loop over every index i in 0..<nums.count, a half-open range that stops before the count.
    6. Add nums[i] to totalSum.
    7. Add nums[i] to rollingSum. The number at i joins the window.
    8. Wait for a full window. The first window numbers have been added when i reaches window - 1, so the check is i >= window - 1.
    9. Record the window. Inside the if, maxSum becomes the larger of itself and rollingSum.
    10. Slide the window. Subtract the oldest number, nums[i - window + 1], from rollingSum. The window that ends at i starts at i - window + 1. After this, rollingSum covers the numbers that precede the next index, and the next iteration adds the new one.
    11. Return totalSum - maxSum after the loop.

    The solution in Swift

    class Solution {
        func removeMinSum(_ nums: [Int], _ k: Int) -> Int {
            let window = nums.count - k
            var rollingSum = 0
            var totalSum = 0
            var maxSum = Int.min
            for i in 0..<nums.count {
                totalSum += nums[i]
                rollingSum += nums[i]
                if i >= window - 1 {
                    maxSum = max(maxSum, rollingSum)
                    rollingSum -= nums[i - window + 1]
                }
            }
            return totalSum - maxSum
        }
    }
    

    Swift details worth knowing:

    • let versus var. window is fixed after it is set, so it is a let. The three running values change in the loop and need var. The compiler warns when a var is never changed, which helps catch mistakes.
    • Parameters are constants. Writing k = nums.count - k inside the method doesn't compile. A new constant is the idiomatic fix.
    • Int.min is a sentinel, not a number to calculate with. It is replaced by the first full window before anything uses it. Adding to it, or negating it, would trap, because Swift stops the program when integer arithmetic overflows.
    • The global max. max(maxSum, rollingSum) is the standard library function that returns the larger of two values.
    • Half-open ranges. 0..<nums.count excludes the upper bound. An empty array gives the empty range 0..<0, which is valid.

    Complexity

    Time: O(n). The loop visits each index once and does a constant amount of work in each pass. The sliding update replaces the O(window) sum that a naive version would compute for each window.

    Space: O(1). Only a few integer variables are used, however large the array is.

    Edge cases and common mistakes

    • k = 0. The window is the whole array. It is full at the last index, so maxSum equals totalSum and the result is 0, which is the sum of no numbers. For [4, 7, 1] with k = 0 the method returns 0.
    • k equal to the array length. The window length is 0, the check i >= window - 1 is true from the start, and the slide step reads nums[i + 1], which runs past the end of the array. In Swift an out-of-range index crashes the program. The code is meant for k smaller than nums.count; with k == nums.count the answer is simply the total sum, so a guard for that case would be needed.
    • All negative numbers. For [-5, -3, -4] with k = 2 the window has one number, and the best one is -3. The total is -12, so the answer is -9. Starting maxSum at 0 would pick an empty window and return -12.
    • Subtracting before checking the window. If the oldest number is removed on every iteration, the window never grows to full size.
    • Off-by-one in the oldest index. The oldest number of a window ending at i is nums[i - window + 1]. Using i - window removes the wrong value.
    • Taking the wrong side. The loop finds the best block to keep. Returning maxSum alone gives the kept sum, not the picked sum.

    Testing

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