Sliding Window & Prefix Sum Solutions: Answer Sheet
This is the answer sheet for the sliding window & prefix sum exercises. Each solution gives the key idea, the optimal complexity, the code in all four languages (use the switcher), and a common pitfall. Try the problems first — then check your work here.
Q1. Maximum average of a size-k window
Idea: This is a fixed-size window. Find the maximum window sum by add-one/subtract-one sliding, then divide by k once at the end (dividing per step just adds rounding noise).
Complexity: O(n) time, O(1) space.
#include <vector>
#include <algorithm>
double maxAverage(const std::vector<int>& nums, int k) {
long long sum = 0;
for (int i = 0; i < k; i++) sum += nums[i];
long long best = sum;
for (int r = k; r < (int)nums.size(); r++) {
sum += nums[r] - nums[r - k];
best = std::max(best, sum);
}
return (double)best / k;
}
double maxAverage(int[] nums, int k) {
long sum = 0;
for (int i = 0; i < k; i++) sum += nums[i];
long best = sum;
for (int r = k; r < nums.length; r++) {
sum += nums[r] - nums[r - k];
best = Math.max(best, sum);
}
return (double) best / k;
}
function maxAverage(nums, k) {
let sum = 0;
for (let i = 0; i < k; i++) sum += nums[i];
let best = sum;
for (let r = k; r < nums.length; r++) {
sum += nums[r] - nums[r - k];
best = Math.max(best, sum);
}
return best / k;
}
def max_average(nums, k):
s = sum(nums[:k])
best = s
for r in range(k, len(nums)):
s += nums[r] - nums[r - k]
best = max(best, s)
return best / k
Pitfall: Comparing averages each step instead of sums invites floating-point error. Track the integer sum; divide once.
Q2. Count windows with sum below a threshold
Idea: Slide a fixed window of size k, maintaining the running sum with add-one/subtract-one, and increment a counter whenever the current window sum is < limit.
Complexity: O(n) time, O(1) space.
#include <vector>
int countWindows(const std::vector<int>& nums, int k, long long limit) {
int n = nums.size();
if (n < k) return 0;
long long sum = 0;
for (int i = 0; i < k; i++) sum += nums[i];
int count = (sum < limit) ? 1 : 0;
for (int r = k; r < n; r++) {
sum += nums[r] - nums[r - k];
if (sum < limit) count++;
}
return count;
}
int countWindows(int[] nums, int k, long limit) {
int n = nums.length;
if (n < k) return 0;
long sum = 0;
for (int i = 0; i < k; i++) sum += nums[i];
int count = (sum < limit) ? 1 : 0;
for (int r = k; r < n; r++) {
sum += nums[r] - nums[r - k];
if (sum < limit) count++;
}
return count;
}
function countWindows(nums, k, limit) {
const n = nums.length;
if (n < k) return 0;
let sum = 0;
for (let i = 0; i < k; i++) sum += nums[i];
let count = sum < limit ? 1 : 0;
for (let r = k; r < n; r++) {
sum += nums[r] - nums[r - k];
if (sum < limit) count++;
}
return count;
}
def count_windows(nums, k, limit):
n = len(nums)
if n < k:
return 0
s = sum(nums[:k])
count = 1 if s < limit else 0
for r in range(k, n):
s += nums[r] - nums[r - k]
if s < limit:
count += 1
return count
Pitfall: Forgetting to test the very first window before the slide loop undercounts by one. Check it after building the first window.
Q3. Longest substring without repeating characters
Idea: A variable-size window. Grow the right edge; when the incoming character is already inside, shrink the left edge until the duplicate is gone. Track the widest valid window.
Complexity: O(n) time, O(min(n, alphabet)) space.
#include <string>
#include <unordered_map>
#include <algorithm>
int lengthOfLongest(const std::string& s) {
std::unordered_map<char, int> count;
int l = 0, best = 0;
for (int r = 0; r < (int)s.size(); r++) {
count[s[r]]++;
while (count[s[r]] > 1) count[s[l++]]--;
best = std::max(best, r - l + 1);
}
return best;
}
import java.util.HashMap;
import java.util.Map;
int lengthOfLongest(String s) {
Map<Character, Integer> count = new HashMap<>();
int l = 0, best = 0;
for (int r = 0; r < s.length(); r++) {
char c = s.charAt(r);
count.merge(c, 1, Integer::sum);
while (count.get(c) > 1) count.merge(s.charAt(l++), -1, Integer::sum);
best = Math.max(best, r - l + 1);
}
return best;
}
function lengthOfLongest(s) {
const count = new Map();
let l = 0, best = 0;
for (let r = 0; r < s.length; r++) {
const c = s[r];
count.set(c, (count.get(c) || 0) + 1);
while (count.get(c) > 1) {
count.set(s[l], count.get(s[l]) - 1);
l++;
}
best = Math.max(best, r - l + 1);
}
return best;
}
def length_of_longest(s):
count = {}
l = best = 0
for r in range(len(s)):
c = s[r]
count[c] = count.get(c, 0) + 1
while count[c] > 1:
count[s[l]] -= 1
l += 1
best = max(best, r - l + 1)
return best
Pitfall: Jumping
ldirectly to “last seen index + 1” works but only if you guard against movinglbackward; the shrink-while-duplicate version above avoids that trap entirely.
Q4. Smallest subarray with sum ≥ target
Idea: With positive values, growing the window only increases the sum and shrinking only decreases it — so a variable-size window works: expand r to reach the target, then shrink l as far as it stays >= target, recording the smallest width.
Complexity: O(n) time, O(1) space.
#include <vector>
#include <climits>
int minSubArrayLen(const std::vector<int>& nums, int target) {
int l = 0, best = INT_MAX;
long long sum = 0;
for (int r = 0; r < (int)nums.size(); r++) {
sum += nums[r];
while (sum >= target) {
best = std::min(best, r - l + 1);
sum -= nums[l++];
}
}
return best == INT_MAX ? 0 : best;
}
int minSubArrayLen(int[] nums, int target) {
int l = 0, best = Integer.MAX_VALUE;
long sum = 0;
for (int r = 0; r < nums.length; r++) {
sum += nums[r];
while (sum >= target) {
best = Math.min(best, r - l + 1);
sum -= nums[l++];
}
}
return best == Integer.MAX_VALUE ? 0 : best;
}
function minSubArrayLen(nums, target) {
let l = 0, best = Infinity, sum = 0;
for (let r = 0; r < nums.length; r++) {
sum += nums[r];
while (sum >= target) {
best = Math.min(best, r - l + 1);
sum -= nums[l++];
}
}
return best === Infinity ? 0 : best;
}
def min_subarray_len(nums, target):
l = 0
best = float("inf")
s = 0
for r in range(len(nums)):
s += nums[r]
while s >= target:
best = min(best, r - l + 1)
s -= nums[l]
l += 1
return 0 if best == float("inf") else best
Pitfall: This monotonic shrink is only valid for non-negative values. With negatives, the sum isn’t monotonic in window size — switch to a prefix sum approach.
Q5. Subarray sum equals k
Idea: A sliding window fails here because negatives break monotonicity. Use prefix sums with a hash map: a subarray ending at r sums to k when some earlier prefix equals current - k. Count those earlier prefixes as you go.
Complexity: O(n) time, O(n) space.
#include <vector>
#include <unordered_map>
int subarraySum(const std::vector<int>& nums, int k) {
std::unordered_map<long long, int> seen;
seen[0] = 1;
long long sum = 0;
int count = 0;
for (int x : nums) {
sum += x;
auto it = seen.find(sum - k);
if (it != seen.end()) count += it->second;
seen[sum]++;
}
return count;
}
import java.util.HashMap;
import java.util.Map;
int subarraySum(int[] nums, int k) {
Map<Long, Integer> seen = new HashMap<>();
seen.put(0L, 1);
long sum = 0;
int count = 0;
for (int x : nums) {
sum += x;
count += seen.getOrDefault(sum - k, 0);
seen.merge(sum, 1, Integer::sum);
}
return count;
}
function subarraySum(nums, k) {
const seen = new Map();
seen.set(0, 1);
let sum = 0, count = 0;
for (const x of nums) {
sum += x;
count += seen.get(sum - k) || 0;
seen.set(sum, (seen.get(sum) || 0) + 1);
}
return count;
}
def subarray_sum(nums, k):
seen = {0: 1}
s = 0
count = 0
for x in nums:
s += x
count += seen.get(s - k, 0)
seen[s] = seen.get(s, 0) + 1
return count
Pitfall: Omitting the
{0: 1}seed misses every subarray that starts at index 0. Also, look upsum - kbefore insertingsum.
Q6. Longest substring with at most K distinct characters
Idea: A variable-size window holding a frequency map. Grow r; whenever the map has more than k distinct keys, shrink l, removing keys whose count hits zero. Record the widest valid window.
Complexity: O(n) time, O(k) space.
#include <string>
#include <unordered_map>
#include <algorithm>
int longestKDistinct(const std::string& s, int k) {
if (k == 0) return 0;
std::unordered_map<char, int> count;
int l = 0, best = 0;
for (int r = 0; r < (int)s.size(); r++) {
count[s[r]]++;
while ((int)count.size() > k) {
if (--count[s[l]] == 0) count.erase(s[l]);
l++;
}
best = std::max(best, r - l + 1);
}
return best;
}
import java.util.HashMap;
import java.util.Map;
int longestKDistinct(String s, int k) {
if (k == 0) return 0;
Map<Character, Integer> count = new HashMap<>();
int l = 0, best = 0;
for (int r = 0; r < s.length(); r++) {
count.merge(s.charAt(r), 1, Integer::sum);
while (count.size() > k) {
char left = s.charAt(l);
if (count.merge(left, -1, Integer::sum) == 0) count.remove(left);
l++;
}
best = Math.max(best, r - l + 1);
}
return best;
}
function longestKDistinct(s, k) {
if (k === 0) return 0;
const count = new Map();
let l = 0, best = 0;
for (let r = 0; r < s.length; r++) {
count.set(s[r], (count.get(s[r]) || 0) + 1);
while (count.size > k) {
const left = s[l];
count.set(left, count.get(left) - 1);
if (count.get(left) === 0) count.delete(left);
l++;
}
best = Math.max(best, r - l + 1);
}
return best;
}
def longest_k_distinct(s, k):
if k == 0:
return 0
count = {}
l = best = 0
for r in range(len(s)):
count[s[r]] = count.get(s[r], 0) + 1
while len(count) > k:
count[s[l]] -= 1
if count[s[l]] == 0:
del count[s[l]]
l += 1
best = max(best, r - l + 1)
return best
Pitfall: Failing to delete a key when its count drops to zero makes
count.sizeoverstate the distinct count, shrinking the window too aggressively.
Q7. Range sum queries (immutable)
Idea: Build a prefix sum array once with a leading zero; then sumRange(l, r) = prefix[r+1] - prefix[l] answers each query in O(1).
Complexity: O(n) build time, O(n) space, O(1) per query.
#include <vector>
class NumArray {
std::vector<long long> prefix;
public:
NumArray(const std::vector<int>& nums) : prefix(nums.size() + 1, 0) {
for (int i = 0; i < (int)nums.size(); i++)
prefix[i + 1] = prefix[i] + nums[i];
}
long long sumRange(int l, int r) { return prefix[r + 1] - prefix[l]; }
};
class NumArray {
private final long[] prefix;
NumArray(int[] nums) {
prefix = new long[nums.length + 1];
for (int i = 0; i < nums.length; i++)
prefix[i + 1] = prefix[i] + nums[i];
}
long sumRange(int l, int r) { return prefix[r + 1] - prefix[l]; }
}
class NumArray {
constructor(nums) {
this.prefix = new Array(nums.length + 1).fill(0);
for (let i = 0; i < nums.length; i++)
this.prefix[i + 1] = this.prefix[i] + nums[i];
}
sumRange(l, r) { return this.prefix[r + 1] - this.prefix[l]; }
}
class NumArray:
def __init__(self, nums):
self.prefix = [0] * (len(nums) + 1)
for i in range(len(nums)):
self.prefix[i + 1] = self.prefix[i] + nums[i]
def sum_range(self, l, r):
return self.prefix[r + 1] - self.prefix[l]
Pitfall: This assumes the array never changes. If values get updated between queries, a static prefix sum goes stale — use a Fenwick tree for O(log n) updates and queries.
That’s the full answer sheet. Back to the exercises or continue to trees.