Sliding window
Solve “best run of consecutive items” problems in O(n) by updating a window as it moves: fixed-size windows, growing and shrinking windows, and windows with a dictionary.
Fixed-size window
{ 4, 2, 7, 1, 8, 3, 5, 6, 2, 9 } tonnes. Which 3 consecutive days were the busiest? Adding up each 3-day window separately does k additions per window: O(n·k). But two neighbouring windows share all but one day, so moving the window needs only one addition and one subtraction.
static (int best, int start) MaxWindowSum(int[] a, int k){ int sum = 0; for (int i = 0; i < k; i++) sum += a[i]; // the first window int best = sum, start = 0; for (int r = k; r < a.Length; r++) { sum += a[r] - a[r - k]; // add the new day, drop the oldest if (sum > best) { best = sum; start = r - k + 1; } } return (best, start);} int[] loads = { 4, 2, 7, 1, 8, 3, 5, 6, 2, 9 };var (most, firstDay) = MaxWindowSum(loads, 3);Console.WriteLine($"Busiest 3 days: {most} t, starting day {firstDay}");Busiest 3 days: 17 t, starting day 7
Variable-size window: grow and shrink
Move rightone step and add that item (grow).While the window breaks the rule (sum > limit), remove the item at leftand moveleftforward (shrink).The window is now valid. Record it if it is the best so far.
static (int length, int start) LongestWithin(int[] a, int limit){ int left = 0, sum = 0, bestLen = 0, bestStart = 0; for (int right = 0; right < a.Length; right++) { sum += a[right]; // grow on the right while (sum > limit) // too much: shrink from the left sum -= a[left++]; if (right - left + 1 > bestLen) { bestLen = right - left + 1; bestStart = left; } } return (bestLen, bestStart);} int[] loads = { 4, 2, 7, 1, 8, 3, 5, 6, 2, 9 };var (days, startDay) = LongestWithin(loads, 12);Console.WriteLine($"Longest run within 12 t: {days} days from day {startDay}");Longest run within 12 t: 3 days from day 1
right moves n times and left moves at most n times in total, because it never goes back. Count the total moves of each pointer, not the nesting of the loops.
A window with a dictionary
left can jump straight past the earlier copy.
static int LongestUnique(string s){ var lastSeen = new Dictionary<char, int>(); int left = 0, best = 0; for (int right = 0; right < s.Length; right++) { if (lastSeen.TryGetValue(s[right], out int prev) && prev >= left) left = prev + 1; // jump past the earlier copy lastSeen[s[right]] = right; best = Math.Max(best, right - left + 1); } return best;} Console.WriteLine(LongestUnique("abcabcbb")); // "abc"Console.WriteLine(LongestUnique("pwwkew")); // "wke"3 3
| Problem says… | Window |
|---|---|
MinLengthAtLeast(int[] a, int target) that returns the length of the shortest run of consecutive positive numbers whose sum is at least target, or 0 if there is none. { 2, 3, 1, 2, 4, 3 } with target 7 → 2 (the run 4, 3). { 1, 1, 1 } with target 5 → 0.
Show a hint
Show the solution
static int MinLengthAtLeast(int[] a, int target){ int left = 0, sum = 0, best = int.MaxValue; for (int right = 0; right < a.Length; right++) { sum += a[right]; while (sum >= target) // valid: try to make it shorter { best = Math.Min(best, right - left + 1); sum -= a[left++]; } } return best == int.MaxValue ? 0 : best;} Console.WriteLine(MinLengthAtLeast(new[] { 2, 3, 1, 2, 4, 3 }, 7));Console.WriteLine(MinLengthAtLeast(new[] { 1, 1, 1 }, 5));2 0
Remember
A sliding window updates a run of consecutive items with what enters and leaves, instead of recomputing it. Fixed-size windows cost one add and one remove per step: O(n) instead of O(n·k). Variable windows grow on the right and shrink on the left; both pointers only move forward, so it is O(n). Grow-and-shrink on sums needs non-negative values; a dictionary inside the window handles “distinct” rules.