Problem overview
The method maxSum receives an array nums of integers and a positive integer k. It looks at every continuous subarray of length exactly k and returns the first and last index of the one with the largest sum, as [start, end].
For nums = [5, 6, 1, 2, 6, 6, 4, 3] and k = 3, the window 6, 6, 4 at indices 4 to 6 has the largest sum, 16, so the result is [4, 6].
Approach: slide the window instead of re-adding it
The straightforward solution adds up the k elements of every window, which costs O(n * k). But two neighbouring windows share k - 1 elements. The window starting at i - 1 covers indices i - 1 to i + k - 2, and the window starting at i covers i to i + k - 1. Moving from one to the next drops the element at i - 1 and picks up the element at i + k - 1.
So the new sum is the old sum, minus the element that left, plus the element that entered. That is two operations for each window, however large k is. This technique is called a sliding window. You compute the first window directly, then slide it across the array once.
On the example, the first window 5, 6, 1 sums to 12. Sliding to the next window removes 5 and adds 2: 12 - 5 + 2 = 9, which is the sum of 6, 1, 2. Sliding again removes 6 and adds 6, giving 9. Then the sums go 14, 16 and 13, and 16 is the best.
Step by step
- Start with the first window as the answer.
answeris[0, k - 1]. If no later window beats the first, this is already correct. An empty initial answer would return nothing in that case. - Prepare
sum, starting at 0, for the sum of the current window. - Add up the first window with a loop over indices 0 to
k - 1. This is the only place wherekelements are added together. - Record the best sum so far.
maxSumstarts equal tosum, because the first window is the only one seen. - Loop over the remaining starts, from 1 to
nums.length - kinclusive. The comparison is<=, because the last window starts atnums.length - k. - Slide the window. Subtract
nums[i - 1], which just left, and addnums[i + k - 1], which just entered. - Compare. When the new
sumis strictly greater thanmaxSum, updatemaxSumand setanswerto[i, i + k - 1]. With a strict>, ties keep the earliest window. - Return
answerwhen the loop is over.
The solution in TypeScript
class Solution {
maxSum(nums: number[], k: number): number[] {
let answer: number[] = [0, k - 1];
let sum = 0;
for (let i = 0; i < k; i++) {
sum += nums[i];
}
let maxSum = sum;
for (let i = 1; i <= nums.length - k; i++) {
sum = sum - nums[i - 1] + nums[i + k - 1];
if (sum > maxSum) {
maxSum = sum;
answer = [i, i + k - 1];
}
}
return answer;
}
}
Some TypeScript details:
- Two loops, both named
i. Eachlet iis scoped to its ownforstatement, so the second loop starts fresh. Reusing the name is fine. - Inferred types.
let sum = 0is inferred asnumber, andlet maxSum = sumcopies that type. Onlyanswerneeds an annotation, to say it is an array of numbers. - The loop bound is an expression.
nums.length - kis recomputed on each check. That costs almost nothing, but you could store it in aconstif you prefer. - No integer overflow. TypeScript numbers are doubles, so repeated adding and subtracting cannot wrap around. Whole numbers stay exact as long as every value stays below 2^53, far beyond the sums in this lesson.
- Reassigning with one expression.
sum = sum - nums[i - 1] + nums[i + k - 1]reads the oldsumon the right before assigning the new one, so no temporary variable is needed.sum += nums[i + k - 1] - nums[i - 1]is an equivalent shorter form.
Complexity
Time: O(n). The first loop does k additions and the second does about n - k slides, each with a constant amount of work. In total, each element enters the window once and leaves once.
Space: O(1). The method keeps a few numbers and the two-element answer.
Edge cases and common mistakes
- The first window is best. For
[9, 1, 1]andk = 2the answer is[0, 1], which is whyanswerstarts as the first window. kequals the length. For[7, 1]andk = 2the second loop does not run, sincenums.length - kis 0 and it starts at 1. The method returns[0, 1].- Ties. For
[4, 4, 4]andk = 2both windows sum to 8, and the result is[0, 1]. - Negative values. For
[-3, -1, -2]andk = 1the result is[1, 1]. BecausemaxSumstarts from the first window's real sum rather than 0, negative sums work. - Off-by-one in the entering element. The element that enters the window starting at
iisnums[i + k - 1]. Usingnums[i + k]reads one position too far, and for the last window it would readundefinedand turn the sum intoNaN. - Using
<instead of<=in the second loop. The last window would never be checked, and the answer could miss it even when it is the best one.
Testing
[5, 6, 1, 2, 6, 6, 4, 3]withk = 3gives[4, 6].[9, 1, 1]withk = 2gives[0, 1].[4, 4, 4]withk = 2gives[0, 1].[-3, -1, -2]withk = 1gives[1, 1].