Answer questions about contiguous stretches in one pass, with a window of fixed size or one that grows and shrinks.
The window always holds exactly the values from left to right, and its running total always equals their sum.
Use it when the question is about a contiguous stretch and asks for a best value, a count, the longest or the shortest. Signal words: consecutive, in a row, a run, a session.
Fixed window (the size k is given): build the first window of k values, then slide. Each slide adds values[right] and subtracts values[right - k].
Variable window (the size is the answer): move right on every step. While the window breaks the rule, move left. Record the answer when the window is valid.
Longest or shortest? For the longest valid window, record after the shrink loop. For the shortest window that reaches a target, record inside the shrink loop, before each value leaves.
It needs a rule that stays broken as the window grows. Non-negative values with a sum limit have this property. Negative values do not.
Cost: each value joins once and leaves at most once, so the whole pass is O(n).
def best_window_sum(values, k):
# [1] Build the first window: the first k values.
window = 0
for i in range(k):
window += values[i]
best = window
# [2] Slide: the value at right joins, the value at right - k leaves.
for right in range(k, len(values)):
window += values[right] - values[right - k]
# [3] Keep the best total seen so far.
best = max(best, window)
return bestpublic long bestWindowSum(int[] values, int k) {
// [1] Build the first window: the first k values.
long window = 0; // 64-bit, because totals can pass 2^31
for (int i = 0; i < k; i++) {
window += values[i];
}
long best = window;
// [2] Slide: the value at right joins, the value at right - k leaves.
for (int right = k; right < values.length; right++) {
window += values[right] - values[right - k];
// [3] Keep the best total seen so far.
best = Math.max(best, window);
}
return best;
}long long bestWindowSum(const vector<int>& values, int k) {
// [1] Build the first window: the first k values.
long long window = 0; // 64-bit, because totals can pass 2^31
for (int i = 0; i < k; i++) {
window += values[i];
}
long long best = window;
// [2] Slide: the value at right joins, the value at right - k leaves.
for (int right = k; right < (int) values.size(); right++) {
window += values[right] - values[right - k];
// [3] Keep the best total seen so far.
best = max(best, window);
}
return best;
}function bestWindowSum(values, k) {
// [1] Build the first window: the first k values.
let window = 0;
for (let i = 0; i < k; i++) {
window += values[i];
}
let best = window;
// [2] Slide: the value at right joins, the value at right - k leaves.
for (let right = k; right < values.length; right++) {
window += values[right] - values[right - k];
// [3] Keep the best total seen so far.
best = Math.max(best, window);
}
return best;
}def longest_within_budget(values, budget):
left = 0
window = 0
best = 0
for right in range(len(values)):
# [1] The value at right joins the window.
window += values[right]
# [2] While the window breaks the rule, the value at left leaves.
while window > budget:
window -= values[left]
left += 1
# [3] The window is valid again: record its length.
best = max(best, right - left + 1)
return bestpublic int longestWithinBudget(int[] values, int budget) {
int left = 0;
long window = 0;
int best = 0;
for (int right = 0; right < values.length; right++) {
// [1] The value at right joins the window.
window += values[right];
// [2] While the window breaks the rule, the value at left leaves.
while (window > budget) {
window -= values[left];
left++;
}
// [3] The window is valid again: record its length.
best = Math.max(best, right - left + 1);
}
return best;
}int longestWithinBudget(const vector<int>& values, int budget) {
int left = 0;
long long window = 0;
int best = 0;
for (int right = 0; right < (int) values.size(); right++) {
// [1] The value at right joins the window.
window += values[right];
// [2] While the window breaks the rule, the value at left leaves.
while (window > budget) {
window -= values[left];
left++;
}
// [3] The window is valid again: record its length.
best = max(best, right - left + 1);
}
return best;
}function longestWithinBudget(values, budget) {
let left = 0;
let window = 0;
let best = 0;
for (let right = 0; right < values.length; right++) {
// [1] The value at right joins the window.
window += values[right];
// [2] While the window breaks the rule, the value at left leaves.
while (window > budget) {
window -= values[left];
left++;
}
// [3] The window is valid again: record its length.
best = Math.max(best, right - left + 1);
}
return best;
}def shortest_reaching_target(values, target):
n = len(values)
left = 0
window = 0
best = n + 1
for right in range(n):
# [1] The value at right joins the window.
window += values[right]
# [2] While the window still reaches the target, record it, then the value at left leaves.
while window >= target:
best = min(best, right - left + 1)
window -= values[left]
left += 1
# [3] If no window reached the target, report 0.
return best if best <= n else 0public int shortestReachingTarget(int[] values, long target) {
int n = values.length;
int left = 0;
long window = 0;
int best = n + 1;
for (int right = 0; right < n; right++) {
// [1] The value at right joins the window.
window += values[right];
// [2] While the window still reaches the target, record it, then the value at left leaves.
while (window >= target) {
best = Math.min(best, right - left + 1);
window -= values[left];
left++;
}
}
// [3] If no window reached the target, report 0.
return best <= n ? best : 0;
}int shortestReachingTarget(const vector<int>& values, long long target) {
int n = values.size();
int left = 0;
long long window = 0;
int best = n + 1;
for (int right = 0; right < n; right++) {
// [1] The value at right joins the window.
window += values[right];
// [2] While the window still reaches the target, record it, then the value at left leaves.
while (window >= target) {
best = min(best, right - left + 1);
window -= values[left];
left++;
}
}
// [3] If no window reached the target, report 0.
return best <= n ? best : 0;
}function shortestReachingTarget(values, target) {
const n = values.length;
let left = 0;
let window = 0;
let best = n + 1;
for (let right = 0; right < n; right++) {
// [1] The value at right joins the window.
window += values[right];
// [2] While the window still reaches the target, record it, then the value at left leaves.
while (window >= target) {
best = Math.min(best, right - left + 1);
window -= values[left];
left++;
}
}
// [3] If no window reached the target, report 0.
return best <= n ? best : 0;
}Kestrel Pay wants the shortest stretch of hours with a net total of at least 167. Refunds make some hours negative.
if window >= target: # bug: this must be a while loop
best = min(best, right - left + 1)
window -= values[left]
left += 1Sofia brings a different question from the content team: which three hours had the most play starts, even if they are not next to each other? For [4, 2, 7, 1, 8, 3], the answer is hours 4, 2 and 0, with 8, 7 and 4 thousand.
systemdesignmastery.dev/dsa/cheatsheets/fixed-and-variable-windows