You are given an array of integers nums and an integer k. There is a sliding window of size k that starts at the left edge of the array. The window slides one position to the right until it reaches the right edge of the array.
Return a list that contains the maximum element in the window at each step.
Example 1:
Input: nums =[1,2,1,0,4,2,6], k =3Output:[2,2,4,4,6]Explanation:Window position Max--------------------[121]042621[210]426212[104]264121[042]641210[426]6
You should aim for a solution as good or better than O(nlogn) time and O(n) space, where n is the size of the input array.
Hint 1
A brute force solution would involve iterating through each window of size k and finding the maximum element within the window by iterating through it. This would be an O(n * k) solution. Can you think of a better way? Maybe think of a data structure that tells the current maximum element of the window in O(1) time.
Hint 2
A heap is the best data structure to use when dealing with maximum or minimum values and it takes O(1) time to get the max or min value. Here, we use a max-heap. But what should we do if the current maximum element is no longer part of the window? Can you think of a different way of adding values to the max-heap?
Hint 3
We process each window by adding elements to the heap along with their indices to track whether the maximum value is still within the current window. As we move from one window to the next, an element may go out of the window but still remain in the max-heap. Is there a way to handle this situation efficiently?
Hint 4
We can ignore those elements that are no longer part of the current window, except when the maximum value is outside the window. In that case, we remove elements from the max-heap until the maximum value belongs to the current window. Why? Because those elements will be eventually removed when the maximum element goes out of the window.
Company Tags
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Sliding Window Technique - Understanding how to maintain a fixed-size window while iterating through an array
Deque (Double-Ended Queue) - Efficiently adding/removing elements from both ends in O(1) time
Monotonic Queue - Maintaining elements in sorted order within a queue for range queries
Heap/Priority Queue - Finding maximum/minimum elements efficiently with lazy deletion
1. Brute Force
Intuition
For every possible window of size k, we simply look at all k elements and pick the maximum. We slide the window one step at a time, and each time we scan all elements inside it to find the max. This method is very easy to understand but slow, because we repeatedly re-scan many of the same elements.
Algorithm
Create an empty list output to store the maximum of each window.
For each starting index i from 0 to len(nums) - k:
Set maxi to nums[i].
Scan all elements from i to i + k - 1 and update maxi.
Where n is the length of the array and k is the size of the window.
2. Segment Tree
Intuition
The brute-force solution recomputes the maximum for every window by scanning all k elements each time, which is slow. A Segment Tree helps us answer “what is the maximum in this range?” much faster after some preprocessing.
Think of the segment tree as a special structure built on top of the array where:
Each node stores the maximum of a segment (range) of the array.
The root stores the maximum of the whole array.
Its children store the maximum of left half and right half, and so on.
Once we build this tree:
We can query the maximum for any range [L, R] (our sliding window) in O(log n) time.
We just slide the window and ask the segment tree for the max in each range.
So the process is: build once → query many times efficiently.
Algorithm
Build the Segment Tree
Start with an array tree big enough to store a full binary tree.
Place the original elements in the leaves (second half of tree).
For each internal node, store the maximum of its two children.
After this step, tree[1] (the root) holds the maximum of the entire array.
Range Maximum Query (Segment Tree query(l, r)):
Shift l and r to point to the leaves representing those indices.
While l <= r in the tree:
If l is a right child, consider tree[l] in the answer and move l to the next segment.
If r is a left child, consider tree[r] in the answer and move r to the previous segment.
Move both l and r up one level by dividing by 2.
Return the maximum found.
Sliding Window using the Segment Tree
For each window starting at index i (from 0 to n - k):
Query the segment tree for the range [i, i + k - 1].
Append this maximum to the output list.
Return the output list containing the maximum for each sliding window.
classSegmentTree:def__init__(self, N, A):
self.n = N
while(self.n &(self.n -1))!=0:
self.n +=1
self.build(N, A)defbuild(self, N, A):
self.tree =[float('-inf')]*(2* self.n)for i inrange(N):
self.tree[self.n + i]= A[i]for i inrange(self.n -1,0,-1):
self.tree[i]=max(self.tree[i <<1], self.tree[i <<1|1])defquery(self, l, r):
res =float('-inf')
l += self.n
r += self.n +1while l < r:if l &1:
res =max(res, self.tree[l])
l +=1if r &1:
r -=1
res =max(res, self.tree[r])
l >>=1
r >>=1return res
classSolution:defmaxSlidingWindow(self, nums, k):
n =len(nums)
segTree = SegmentTree(n, nums)
output =[]for i inrange(n - k +1):
output.append(segTree.query(i, i + k -1))return output
classSegmentTree{int n;int[] tree;SegmentTree(intN,int[]A){this.n =N;while(Integer.bitCount(n)!=1){
n++;}build(N,A);}voidbuild(intN,int[]A){
tree =newint[2* n];Arrays.fill(tree,Integer.MIN_VALUE);for(int i =0; i <N; i++){
tree[n + i]=A[i];}for(int i = n -1; i >0;--i){
tree[i]=Math.max(tree[i <<1], tree[i <<1|1]);}}intquery(int l,int r){int res =Integer.MIN_VALUE;for(l += n, r += n +1; l < r; l >>=1, r >>=1){if((l &1)==1) res =Math.max(res, tree[l++]);if((r &1)==1) res =Math.max(res, tree[--r]);}return res;}}publicclassSolution{publicint[]maxSlidingWindow(int[] nums,int k){int n = nums.length;SegmentTree segTree =newSegmentTree(n, nums);int[] output =newint[n - k +1];for(int i =0; i <= n - k; i++){
output[i]= segTree.query(i, i + k -1);}return output;}}
classSegmentTree{public:int n;
vector<int> tree;SegmentTree(int N, vector<int>& A){this->n = N;while(__builtin_popcount(n)!=1){
n++;}build(N, A);}voidbuild(int N, vector<int>& A){
tree.resize(2* n, INT_MIN);for(int i =0; i < N; i++){
tree[n + i]= A[i];}for(int i = n -1; i >0;--i){
tree[i]=max(tree[i <<1], tree[i <<1|1]);}}intquery(int l,int r){int res = INT_MIN;for(l += n, r += n +1; l < r; l >>=1, r >>=1){if(l &1) res =max(res, tree[l++]);if(r &1) res =max(res, tree[--r]);}return res;}};classSolution{public:
vector<int>maxSlidingWindow(vector<int>& nums,int k){int n = nums.size();
SegmentTree segTree(n, nums);
vector<int> output;for(int i =0; i <= n - k; i++){
output.push_back(segTree.query(i, i + k -1));}return output;}};
classSegmentTree{/**
* @constructor
* @param {number} N
* @param {number[]} A
*/constructor(N,A){this.n =N;while((this.n &(this.n -1))!==0){this.n++;}this.build(N,A);}/**
* @param {number} N
* @param {number[]} A
* @return {void}
*/build(N,A){this.tree =newArray(2*this.n).fill(-Infinity);for(let i =0; i <N; i++){this.tree[this.n + i]=A[i];}for(let i =this.n -1; i >0; i--){this.tree[i]= Math.max(this.tree[i <<1],this.tree[(i <<1)|1]);}}/**
* @param {number} l
* @param {number} r
* @return {number}
*/query(l, r){let res =-Infinity;
l +=this.n;
r +=this.n +1;while(l < r){if(l &1) res = Math.max(res,this.tree[l++]);if(r &1) res = Math.max(res,this.tree[--r]);
l >>=1;
r >>=1;}return res;}}classSolution{/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/maxSlidingWindow(nums, k){let n = nums.length;let segTree =newSegmentTree(n, nums);let output =[];for(let i =0; i <= n - k; i++){
output.push(segTree.query(i, i + k -1));}return output;}}
publicclassSegmentTree{publicint n;publicint[] tree;publicSegmentTree(int N,int[] A){this.n = N;while(System.Numerics.BitOperations.PopCount((uint)n)!=1){
n++;}Build(N, A);}publicvoidBuild(int N,int[] A){
tree =newint[2* n];
Array.Fill(tree,int.MinValue);for(int i =0; i < N; i++){
tree[n + i]= A[i];}for(int i = n -1; i >0;--i){
tree[i]= Math.Max(tree[i <<1], tree[i <<1|1]);}}publicintQuery(int l,int r){int res =int.MinValue;
l += n;
r += n +1;while(l < r){if((l &1)==1) res = Math.Max(res, tree[l++]);if((r &1)==1) res = Math.Max(res, tree[--r]);
l >>=1;
r >>=1;}return res;}}publicclassSolution{publicint[]MaxSlidingWindow(int[] nums,int k){int n = nums.Length;SegmentTree segTree =newSegmentTree(n, nums);int[] output =newint[n - k +1];for(int i =0; i <= n - k; i++){
output[i]= segTree.Query(i, i + k -1);}return output;}}
type SegmentTree struct{
n int
tree []int}funcNewSegmentTree(N int, A []int)*SegmentTree {
n := N
for bits.OnesCount(uint(n))!=1{
n++}
st :=&SegmentTree{
n: n,}
st.build(N, A)return st
}func(st *SegmentTree)build(N int, A []int){
st.tree =make([]int,2*st.n)for i :=range st.tree {
st.tree[i]= math.MinInt
}for i :=0; i < N; i++{
st.tree[st.n+i]= A[i]}for i := st.n -1; i >0; i--{
st.tree[i]=max(st.tree[i<<1], st.tree[i<<1|1])}}func(st *SegmentTree)Query(l, r int)int{
res := math.MinInt
l += st.n
r += st.n +1for l < r {if l&1==1{
res =max(res, st.tree[l])
l++}if r&1==1{
r--
res =max(res, st.tree[r])}
l >>=1
r >>=1}return res
}funcmax(a, b int)int{if a > b {return a
}return b
}funcmaxSlidingWindow(nums []int, k int)[]int{
n :=len(nums)
segTree :=NewSegmentTree(n, nums)
output :=make([]int, n-k+1)for i :=0; i <= n-k; i++{
output[i]= segTree.Query(i, i+k-1)}return output
}
classSegmentTree(N: Int, A: IntArray){privatevar n: Int = N
privatevar tree: IntArray =IntArray(0)init{var size = N
while(Integer.bitCount(size)!=1){
size++}
n = size
build(N, A)}privatefunbuild(N: Int, A: IntArray){
tree =IntArray(2* n)
tree.fill(Int.MIN_VALUE)for(i in0 until N){
tree[n + i]= A[i]}for(i in n -1 downTo 1){
tree[i]=maxOf(tree[i *2], tree[i *2+1])}}funquery(l: Int, r: Int): Int {var res = Int.MIN_VALUE
var left = l + n
var right = r + n +1while(left < right){if(left %2==1){
res =maxOf(res, tree[left])
left++}if(right %2==1){
right--
res =maxOf(res, tree[right])}
left /=2
right /=2}return res
}}class Solution {funmaxSlidingWindow(nums: IntArray, k: Int): IntArray {val n = nums.size
val segTree =SegmentTree(n, nums)val result =IntArray(n - k +1)for(i in0..n - k){
result[i]= segTree.query(i, i + k -1)}return result
}}
classSegmentTree{privatevar n:Intprivatevar tree:[Int]init(_N:Int,_A:[Int]){self.n =Nwhile(self.n &(self.n -1))!=0{self.n +=1}self.tree =[Int](repeating:Int.min, count:2*self.n)build(N,A)}privatefuncbuild(_N:Int,_A:[Int]){for i in0..<N{
tree[n + i]=A[i]}for i instride(from: n -1, through:1, by:-1){
tree[i]=max(tree[i <<1], tree[i <<1|1])}}funcquery(_ l:Int,_ r:Int)->Int{var res =Int.min
var l = l + n
var r = r + n +1while l < r {if l &1!=0{
res =max(res, tree[l])
l +=1}if r &1!=0{
r -=1
res =max(res, tree[r])}
l >>=1
r >>=1}return res
}}classSolution{funcmaxSlidingWindow(_ nums:[Int],_ k:Int)->[Int]{let n = nums.count
let segTree =SegmentTree(n, nums)var output =[Int]()for i in0...(n - k){
output.append(segTree.query(i, i + k -1))}return output
}}
Time & Space Complexity
Time complexity: O(nlogn)
Space complexity: O(n)
3. Heap
Intuition
We want to quickly get the maximum value inside a sliding window that moves across the array. A max-heap is perfect for this because it always lets us access the largest element instantly.
As we slide the window:
We keep inserting new elements into the heap.
Some old elements will fall out of the left side of the window.
If the largest element in the heap is no longer inside the window, we remove it.
The top of the heap always represents the current maximum for the window.
This way, we efficiently maintain the maximum even as the window moves.
Algorithm
Use a max-heap to store pairs of (value, index) for all elements we encounter.
Expand the window by inserting each new element into the heap.
Once the window size becomes k:
Remove elements from the heap if their index is outside the current window.
The top of the heap now gives the maximum for the window.
Add this maximum to the result list.
Continue sliding the window until the end of the array and return all collected maximums.
classSolution:defmaxSlidingWindow(self, nums: List[int], k:int)-> List[int]:
n =len(nums)
leftMax =[0]* n
rightMax =[0]* n
leftMax[0]= nums[0]
rightMax[n -1]= nums[n -1]for i inrange(1, n):if i % k ==0:
leftMax[i]= nums[i]else:
leftMax[i]=max(leftMax[i -1], nums[i])if(n -1- i)% k ==0:
rightMax[n -1- i]= nums[n -1- i]else:
rightMax[n -1- i]=max(rightMax[n - i], nums[n -1- i])
output =[0]*(n - k +1)for i inrange(n - k +1):
output[i]=max(leftMax[i + k -1], rightMax[i])return output
publicclassSolution{publicint[]maxSlidingWindow(int[] nums,int k){int n = nums.length;int[] leftMax =newint[n];int[] rightMax =newint[n];
leftMax[0]= nums[0];
rightMax[n -1]= nums[n -1];for(int i =1; i < n; i++){if(i % k ==0){
leftMax[i]= nums[i];}else{
leftMax[i]=Math.max(leftMax[i -1], nums[i]);}if((n -1- i)% k ==0){
rightMax[n -1- i]= nums[n -1- i];}else{
rightMax[n -1- i]=Math.max(rightMax[n - i], nums[n -1- i]);}}int[] output =newint[n - k +1];for(int i =0; i < n - k +1; i++){
output[i]=Math.max(leftMax[i + k -1], rightMax[i]);}return output;}}
classSolution{public:
vector<int>maxSlidingWindow(vector<int>& nums,int k){int n = nums.size();
vector<int>leftMax(n);
vector<int>rightMax(n);
leftMax[0]= nums[0];
rightMax[n -1]= nums[n -1];for(int i =1; i < n; i++){if(i % k ==0){
leftMax[i]= nums[i];}else{
leftMax[i]=max(leftMax[i -1], nums[i]);}if((n -1- i)% k ==0){
rightMax[n -1- i]= nums[n -1- i];}else{
rightMax[n -1- i]=max(rightMax[n - i], nums[n -1- i]);}}
vector<int>output(n - k +1);for(int i =0; i < n - k +1; i++){
output[i]=max(leftMax[i + k -1], rightMax[i]);}return output;}};
classSolution{/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/maxSlidingWindow(nums, k){const n = nums.length;const leftMax =newArray(n);const rightMax =newArray(n);
leftMax[0]= nums[0];
rightMax[n -1]= nums[n -1];for(let i =1; i < n; i++){if(i % k ===0){
leftMax[i]= nums[i];}else{
leftMax[i]= Math.max(leftMax[i -1], nums[i]);}if((n -1- i)% k ===0){
rightMax[n -1- i]= nums[n -1- i];}else{
rightMax[n -1- i]= Math.max(
rightMax[n - i],
nums[n -1- i],);}}const output =newArray(n - k +1);for(let i =0; i < n - k +1; i++){
output[i]= Math.max(leftMax[i + k -1], rightMax[i]);}return output;}}
publicclassSolution{publicint[]MaxSlidingWindow(int[] nums,int k){int n = nums.Length;int[] leftMax =newint[n];int[] rightMax =newint[n];
leftMax[0]= nums[0];
rightMax[n -1]= nums[n -1];for(int i =1; i < n; i++){if(i % k ==0){
leftMax[i]= nums[i];}else{
leftMax[i]= Math.Max(leftMax[i -1], nums[i]);}if((n -1- i)% k ==0){
rightMax[n -1- i]= nums[n -1- i];}else{
rightMax[n -1- i]= Math.Max(rightMax[n - i], nums[n -1- i]);}}int[] output =newint[n - k +1];for(int i =0; i < n - k +1; i++){
output[i]= Math.Max(leftMax[i + k -1], rightMax[i]);}return output;}}
funcmaxSlidingWindow(nums []int, k int)[]int{
n :=len(nums)
leftMax :=make([]int, n)
rightMax :=make([]int, n)
leftMax[0]= nums[0]
rightMax[n-1]= nums[n-1]for i :=1; i < n; i++{if i%k ==0{
leftMax[i]= nums[i]}else{
leftMax[i]=max(leftMax[i-1], nums[i])}}for i :=1; i < n; i++{if(n-1-i)%k ==0{
rightMax[n-1-i]= nums[n-1-i]}else{
rightMax[n-1-i]=max(rightMax[n-i], nums[n-1-i])}}
output :=make([]int, n-k+1)for i :=0; i < n-k+1; i++{
output[i]=max(leftMax[i+k-1], rightMax[i])}return output
}funcmax(a, b int)int{if a > b {return a
}return b
}
class Solution {funmaxSlidingWindow(nums: IntArray, k: Int): IntArray {val n = nums.size
val leftMax =IntArray(n)val rightMax =IntArray(n)
leftMax[0]= nums[0]
rightMax[n -1]= nums[n -1]for(i in1 until n){if(i % k ==0){
leftMax[i]= nums[i]}else{
leftMax[i]=maxOf(leftMax[i -1], nums[i])}if((n -1- i)% k ==0){
rightMax[n -1- i]= nums[n -1- i]}else{
rightMax[n -1- i]=maxOf(rightMax[n - i], nums[n -1- i])}}val output =IntArray(n - k +1)for(i in0..n - k){
output[i]=maxOf(leftMax[i + k -1], rightMax[i])}return output
}}
classSolution{funcmaxSlidingWindow(_ nums:[Int],_ k:Int)->[Int]{let n = nums.count
var leftMax =[Int](repeating:0, count: n)var rightMax =[Int](repeating:0, count: n)
leftMax[0]= nums[0]
rightMax[n -1]= nums[n -1]for i in1..<n {if i % k ==0{
leftMax[i]= nums[i]}else{
leftMax[i]=max(leftMax[i -1], nums[i])}if(n -1- i)% k ==0{
rightMax[n -1- i]= nums[n -1- i]}else{
rightMax[n -1- i]=max(rightMax[n - i], nums[n -1- i])}}var output =[Int](repeating:0, count: n - k +1)for i in0..<(n - k +1){
output[i]=max(leftMax[i + k -1], rightMax[i])}return output
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
5. Deque
Intuition
A deque helps us efficiently track the maximum inside the sliding window. The key idea is to keep the deque storing indices of elements in decreasing order of their values. This guarantees that:
The front of the deque always holds the index of the current window’s maximum.
Smaller elements behind a bigger one are useless (they can never become the max later), so we remove them when pushing a new number.
If the element at the front falls out of the window, we remove it.
By maintaining this structure, each element is added and removed at most once, giving an optimal solution.
Algorithm
Use a deque to store indices of elements in decreasing order of their values.
Expand the window by moving the right pointer:
Before inserting the new index, remove indices whose values are smaller than the new value (they cannot be future maximums).
Add the new index to the deque.
If the left pointer passes the front index, remove it (it's outside the window).
Once the window reaches size k, the front of the deque represents the maximum — add it to the output.
classSolution:defmaxSlidingWindow(self, nums: List[int], k:int)-> List[int]:
output =[]
q = deque()# index
l = r =0while r <len(nums):while q and nums[q[-1]]< nums[r]:
q.pop()
q.append(r)if l > q[0]:
q.popleft()if(r +1)>= k:
output.append(nums[q[0]])
l +=1
r +=1return output
publicclassSolution{publicint[]maxSlidingWindow(int[] nums,int k){int n = nums.length;int[] output =newint[n - k +1];Deque<Integer> q =newLinkedList<>();int l =0, r =0;while(r < n){while(!q.isEmpty()&& nums[q.getLast()]< nums[r]){
q.removeLast();}
q.addLast(r);if(l > q.getFirst()){
q.removeFirst();}if((r +1)>= k){
output[l]= nums[q.getFirst()];
l++;}
r++;}return output;}}
classSolution{public:
vector<int>maxSlidingWindow(vector<int>& nums,int k){int n = nums.size();
vector<int>output(n - k +1);
deque<int> q;int l =0, r =0;while(r < n){while(!q.empty()&& nums[q.back()]< nums[r]){
q.pop_back();}
q.push_back(r);if(l > q.front()){
q.pop_front();}if((r +1)>= k){
output[l]= nums[q.front()];
l++;}
r++;}return output;}};
classSolution{/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/maxSlidingWindow(nums, k){const n = nums.length;const output =newArray(n - k +1);const q =newDeque();let l =0,
r =0;while(r < n){while(q.size()&& nums[q.back()]< nums[r]){
q.popBack();}
q.pushBack(r);if(l > q.front()){
q.popFront();}if(r +1>= k){
output[l]= nums[q.front()];
l++;}
r++;}return output;}}
publicclassSolution{publicint[]MaxSlidingWindow(int[] nums,int k){int n = nums.Length;int[] output =newint[n - k +1];var q =newLinkedList<int>();int l =0, r =0;while(r < n){while(q.Count >0&& nums[q.Last.Value]< nums[r]){
q.RemoveLast();}
q.AddLast(r);if(l > q.First.Value){
q.RemoveFirst();}if((r +1)>= k){
output[l]= nums[q.First.Value];
l++;}
r++;}return output;}}
funcmaxSlidingWindow(nums []int, k int)[]int{
output :=[]int{}
q :=[]int{}
l, r :=0,0for r <len(nums){forlen(q)>0&& nums[q[len(q)-1]]< nums[r]{
q = q[:len(q)-1]}
q =append(q, r)if l > q[0]{
q = q[1:]}if(r +1)>= k {
output =append(output, nums[q[0]])
l +=1}
r +=1}return output
}
class Solution {funmaxSlidingWindow(nums: IntArray, k: Int): IntArray {val output = mutableListOf<Int>()val q = ArrayDeque<Int>()var l =0var r =0while(r < nums.size){while(q.isNotEmpty()&& nums[q.last()]< nums[r]){
q.removeLast()}
q.addLast(r)if(l > q.first()){
q.removeFirst()}if((r +1)>= k){
output.add(nums[q.first()])
l +=1}
r +=1}return output.toIntArray()}}
classSolution{funcmaxSlidingWindow(_ nums:[Int],_ k:Int)->[Int]{var output =[Int]()var deque =[Int]()// Indexvar l =0, r =0while r < nums.count {while!deque.isEmpty && nums[deque.last!]< nums[r]{
deque.removeLast()}
deque.append(r)if l > deque.first!{
deque.removeFirst()}if(r +1)>= k {
output.append(nums[deque.first!])
l +=1}
r +=1}return output
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
Common Pitfalls
Storing Values Instead of Indices in the Deque
When using the deque approach, a common mistake is storing the actual values rather than their indices. Storing values makes it impossible to determine when an element has left the window, since you cannot compare positions. Always store indices in the deque and use nums[index] to access values.
Incorrect Window Boundary Checks
Many solutions fail by using wrong conditions for when to start recording results or when to shrink the window. The first valid window exists when right >= k - 1 (or equivalently right + 1 >= k). Off-by-one errors here cause either missing the first window's maximum or including results before the window is fully formed.
Not Maintaining Decreasing Order in Deque
The deque must maintain indices in decreasing order of their corresponding values. A common bug is using <= instead of < when comparing values before popping, or forgetting to pop elements altogether. This results in the front of the deque not representing the actual maximum. Always pop from the back while nums[deque.back()] < nums[current].
Forgetting to Handle the Output Array Size
The number of windows is n - k + 1, not n or n - k. Allocating an output array of the wrong size leads to index out of bounds errors or missing results. This is especially problematic when k equals the array length, where there should be exactly one result.
Incorrect Segment Tree Range Queries
When using segment trees, a frequent mistake is confusing inclusive versus exclusive range boundaries. The query range [i, i + k - 1] is inclusive on both ends for a window starting at index i. Mixing up these boundaries causes queries to include elements outside the window or miss elements at the boundaries.