Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit solution and explanation
9 more videos on this problem
Leetcode - Longest Continuous Subarray With Absolute Diff Less Than or Equal to LimitFraz · 10:23 · 31K views
Longest Continuous Subarray with Absolute Diff Less than or Equal to Limit - Leetcode 1438 - PythonNeetCodeIO · 16:37 · 24K views
Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit | Leetcode 1438 |DetailedcodestorywithMIK · 49:02 · 18K views
1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit | 3 ApproachesAryan Mittal · 43:44 · 7K views
花花酱 LeetCode 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit EP323Hua Hua · 15:40 · 6K views
LeetCode 1438: Longest Continuous Subarray With Absolute Diff Less Than or Equal to LimitEngineering Digest · 14:51 · 4K views
LeetCode 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to LimitHappy Coding · 28:47 · 3K views
Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit | Leetcode 1438Ayushi Sharma · 15:48 · 2K views
1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit (Leetcode Medium)Programming Live with Larry · 11:23 · 2K views
Longest Continuous Subarray With Abs Diff less than or equals to limit(LC 1438)(Sliding Window, Map)CodeWithARB · 30:45 · 1K views
The problem
Given an array of integers nums and an integer limit, return the size of the longest non-empty subarray such that the absolute difference between any two elements of this subarray is less than or equal to limit.
Example 1:
Input: nums = [8,2,4,7], limit = 4 Output: 2 Explanation: All subarrays are: [8] with maximum absolute diff |8-8| = 0 <= 4. [8,2] with maximum absolute diff |8-2| = 6 > 4. [8,2,4] with maximum absolute diff |8-2| = 6 > 4. [8,2,4,7] with maximum absolute diff |8-2| = 6 > 4. [2] with maximum absolute diff |2-2| = 0 <= 4. [2,4] with maximum absolute diff |2-4| = 2 <= 4. [2,4,7] with maximum absolute diff |2-7| = 5 > 4. [4] with maximum absolute diff |4-4| = 0 <= 4. [4,7] with maximum absolute diff |4-7| = 3 <= 4. [7] with maximum absolute diff |7-7| = 0 <= 4. Therefore, the size of the longest subarray is 2.
Example 2:
Input: nums = [10,1,2,4,7,2], limit = 5 Output: 4 Explanation: The subarray [2,4,7,2] is the longest since the maximum absolute diff is |2-7| = 5 <= 5.
Example 3:
Input: nums = [4,2,2,2,4,4,2,2], limit = 0 Output: 3
Constraints:
1 <= nums.length <= 1051 <= nums[i] <= 1090 <= limit <= 109
Approach overview
Problem Overview: Given an integer array nums and a limit, find the length of the longest contiguous subarray where the absolute difference between the maximum and minimum elements is less than or equal to the limit.
Approach 1: Sliding Window with Monotonic Deques (O(n) time, O(n) space)
This approach maintains a dynamic window using two pointers. The key challenge is tracking the current window's minimum and maximum efficiently. Two monotonic deques solve this: one decreasing deque stores potential maximum values and one increasing deque stores potential minimum values. As you expand the right pointer, insert the new element while maintaining monotonic order. If max - min exceeds the limit, move the left pointer and remove elements leaving the window from the front of the deques.
Each element enters and leaves the deques at most once, so the total work is linear. This pattern appears frequently in sliding window problems that require maintaining window extrema efficiently. The deques act as a monotonic queue, allowing constant-time access to the current max and min.
Approach 2: Sliding Window with TreeMap (O(n log n) time, O(n) space)
This version uses a balanced ordered map such as Java's TreeMap. The sliding window expands by inserting each number into the map and tracking its frequency. The smallest and largest keys represent the current window's minimum and maximum. If the difference exceeds the limit, move the left pointer and decrement the frequency of the outgoing value, removing it when the count reaches zero.
Each insertion and deletion costs O(log n) due to tree balancing. The advantage is conceptual simplicity since the ordered map automatically keeps elements sorted. This approach is useful when a language provides efficient ordered set or map implementations but monotonic deque patterns feel less familiar.
Recommended for interviews: The monotonic deque sliding window is the expected optimal solution with O(n) time. It demonstrates mastery of window invariants and specialized queue structures. The TreeMap approach still shows solid understanding but is usually considered a step below the optimal linear solution.
Approach 1: Sliding Window with Deques
This approach uses the sliding window technique along with two deques to efficiently keep track of the maximum and minimum values in the current window.
We maintain two deques - one for the indices of the maximum values and another for the minimum values, within the current window of the array, called `nums`. By utilizing the sliding window, we can dynamically adjust the start and end of the window based on whether the condition of the absolute difference being less than or equal to the limit is satisfied.
- Initialize two pointers `left` and `right` to represent the bounds of the sliding window.
- Iterate with the `right` pointer over each element of the array.
- Use the deques to quickly obtain the maximum and minimum values in the current window.
- If the difference between the maximum and minimum exceeds the limit, increment the `left` pointer to attempt reducing the window size.
- Keep track of the maximum size of the window that satisfies the condition.
from collections import deque
def longest_subarray(nums, limit):
min_deque = deque()
max_deque = deque()
left = 0
max_length = 0
for right in range(len(nums)):
while min_deque and nums[min_deque[-1]] > nums[right]:
min_deque.pop()
while max_deque and nums[max_deque[-1]] < nums[right]:
max_deque.pop()
min_deque.append(right)
max_deque.append(right)
while nums[max_deque[0]] - nums[min_deque[0]] > limit:
left += 1
if min_deque[0] < left:
min_deque.popleft()
if max_deque[0] < left:
max_deque.popleft()
max_length = max(max_length, right - left + 1)
return max_lengthimport java.util.Deque;
import java.util.LinkedList;
public int longestSubarray(int[] nums, int limit) {
Deque<Integer> minDeque = new LinkedList<>();
Deque<Integer> maxDeque = new LinkedList<>();
int left = 0;
int maxLength = 0;
for (int right = 0; right < nums.length; right++) {
while (!minDeque.isEmpty() && nums[minDeque.peekLast()] > nums[right])
minDeque.pollLast();
while (!maxDeque.isEmpty() && nums[maxDeque.peekLast()] < nums[right])
maxDeque.pollLast();
minDeque.offerLast(right);
maxDeque.offerLast(right);
while (nums[maxDeque.peekFirst()] - nums[minDeque.peekFirst()] > limit) {
left++;
if (minDeque.peekFirst() < left)
minDeque.pollFirst();
if (maxDeque.peekFirst() < left)
maxDeque.pollFirst();
}
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}#include <deque>
#include <vector>
using namespace std;
int longestSubarray(vector<int>& nums, int limit) {
deque<int> minDeque, maxDeque;
int left = 0, maxLength = 0;
for (int right = 0; right < nums.size(); ++right) {
while (!minDeque.empty() && nums[minDeque.back()] > nums[right])
minDeque.pop_back();
while (!maxDeque.empty() && nums[maxDeque.back()] < nums[right])
maxDeque.pop_back();
minDeque.push_back(right);
maxDeque.push_back(right);
while (nums[maxDeque.front()] - nums[minDeque.front()] > limit) {
++left;
if (minDeque.front() < left) minDeque.pop_front();
if (maxDeque.front() < left) maxDeque.pop_front();
}
maxLength = max(maxLength, right - left + 1);
}
return maxLength;
}function longestSubarray(nums, limit) {
let minDeque = [];
let maxDeque = [];
let left = 0;
let maxLength = 0;
for (let right = 0; right < nums.length; right++) {
while (minDeque.length && nums[minDeque[minDeque.length - 1]] > nums[right])
minDeque.pop();
while (maxDeque.length && nums[maxDeque[maxDeque.length - 1]] < nums[right])
maxDeque.pop();
minDeque.push(right);
maxDeque.push(right);
while (nums[maxDeque[0]] - nums[minDeque[0]] > limit) {
left++;
if (minDeque[0] < left) minDeque.shift();
if (maxDeque[0] < left) maxDeque.shift();
}
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}using System;
using System.Collections.Generic;
public class Solution {
public int LongestSubarray(int[] nums, int limit) {
var minDeque = new LinkedList<int>();
var maxDeque = new LinkedList<int>();
int left = 0;
int maxLength = 0;
for (int right = 0; right < nums.Length; right++) {
while (minDeque.Count != 0 && nums[minDeque.Last.Value] > nums[right])
minDeque.RemoveLast();
while (maxDeque.Count != 0 && nums[maxDeque.Last.Value] < nums[right])
maxDeque.RemoveLast();
minDeque.AddLast(right);
maxDeque.AddLast(right);
while (nums[maxDeque.First.Value] - nums[minDeque.First.Value] > limit) {
left++;
if (minDeque.First.Value < left)
minDeque.RemoveFirst();
if (maxDeque.First.Value < left)
maxDeque.RemoveFirst();
}
maxLength = Math.Max(maxLength, right - left + 1);
}
return maxLength;
}
}The python implementation uses two deques, `min_deque` and `max_deque`, to maintain the indices of the current window's minimum and maximum elements.
As we iterate with the `right` pointer across each element, we adjust the deques to hold only the valid elements within the current window range. If at any point the difference between the max and min exceeds the specified limit, the `left` pointer is incremented to reduce the window size.
Time Complexity: O(n), where n is the length of the array, because each element is added and removed from the deque at most once.
Space Complexity: O(n), due to the size of the deques which in the worst case can hold all elements.
Approach 2: Sliding Window with TreeMap (Java)
This approach uses a sliding window implemented with a TreeMap to automatically manage and get minimum and maximum values from the current window range.
- The TreeMap data structure provides built-in sorting and easy retrieval for both floor (minimum) and ceiling (maximum) elements.
- By expanding the window with the right pointer, the map logs occurrences of each element within the window.
- On conflicting with the limit constraint, the left pointer removes items from the TreeMap to reevaluate conditions.
import java.util.TreeMap;
public int longestSubarray(int[] nums, int limit) {
TreeMap<Integer, Integer> map = new TreeMap<>();
int left = 0;
int maxLength = 0;
for (int right = 0; right < nums.length; right++) {
map.put(nums[right], map.getOrDefault(nums[right], 0) + 1);
while (map.lastKey() - map.firstKey() > limit) {
map.put(nums[left], map.get(nums[left]) - 1);
if (map.get(nums[left]) == 0) {
map.remove(nums[left]);
}
left++;
}
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}By leveraging the automatic ordering of keys within a TreeMap, this approach ensures the retrieval and conditional check of both minimum and maximum values within a determined window is efficient.
Upon the boundary violation concerning the permissible limit, the left pointer incrementally adjusts, pruning the TreeMap until the condition is restored.
Time Complexity: O(n log n); each insertion and deletion operation on TreeMap takes log n time.
Space Complexity: O(n); due to the additional space required by the TreeMap.
Approach 3: Ordered Set + Sliding Window
We can enumerate each position as the right endpoint of the subarray, and find the leftmost left endpoint corresponding to it, such that the difference between the maximum and minimum values in the interval does not exceed limit. During the process, we use an ordered set to maintain the maximum and minimum values within the window.
The time complexity is O(n log n), and the space complexity is O(n). Here, n is the length of the array nums.
function longestSubarray(nums: number[], limit: number): number {
const ts = new TreapMultiSet<number>();
let ans = 0;
let j = 0;
for (let i = 0; i < nums.length; ++i) {
ts.add(nums[i]);
while (ts.last()! - ts.first()! > limit) {
ts.delete(nums[j++]);
}
ans = Math.max(ans, i - j + 1);
}
return ans;
}
type CompareFunction<T, R extends 'number' | 'boolean'> = (
a: T,
b: T,
) => R extends 'number' ? number : boolean;
interface ITreapMultiSet<T> extends Iterable<T> {
add: (...value: T[]) => this;
has: (value: T) => boolean;
delete: (value: T) => void;
bisectLeft: (value: T) => number;
bisectRight: (value: T) => number;
indexOf: (value: T) => number;
lastIndexOf: (value: T) => number;
at: (index: number) => T | undefined;
first: () => T | undefined;
last: () => T | undefined;
lower: (value: T) => T | undefined;
higher: (value: T) => T | undefined;
floor: (value: T) => T | undefined;
ceil: (value: T) => T | undefined;
shift: () => T | undefined;
pop: (index?: number) => T | undefined;
count: (value: T) => number;
keys: () => IterableIterator<T>;
values: () => IterableIterator<T>;
rvalues: () => IterableIterator<T>;
entries: () => IterableIterator<[number, T]>;
readonly size: number;
}
class TreapNode<T = number> {
value: T;
count: number;
size: number;
priority: number;
left: TreapNode<T> | null;
right: TreapNode<T> | null;
constructor(value: T) {
this.value = value;
this.count = 1;
this.size = 1;
this.priority = Math.random();
this.left = null;
this.right = null;
}
static getSize(node: TreapNode<any> | null): number {
return node?.size ?? 0;
}
static getFac(node: TreapNode<any> | null): number {
return node?.priority ?? 0;
}
pushUp(): void {
let tmp = this.count;
tmp += TreapNode.getSize(this.left);
tmp += TreapNode.getSize(this.right);
this.size = tmp;
}
rotateRight(): TreapNode<T> {
// eslint-disable-next-line @typescript-eslint/no-this-alias
let node: TreapNode<T> = this;
const left = node.left;
node.left = left?.right ?? null;
left && (left.right = node);
left && (node = left);
node.right?.pushUp();
node.pushUp();
return node;
}
rotateLeft(): TreapNode<T> {
// eslint-disable-next-line @typescript-eslint/no-this-alias
let node: TreapNode<T> = this;
const right = node.right;
node.right = right?.left ?? null;
right && (right.left = node);
right && (node = right);
node.left?.pushUp();
node.pushUp();
return node;
}
}
class TreapMultiSet<T = number> implements ITreapMultiSet<T> {
private readonly root: TreapNode<T>;
private readonly compareFn: CompareFunction<T, 'number'>;
private readonly leftBound: T;
private readonly rightBound: T;
constructor(compareFn?: CompareFunction<T, 'number'>);
constructor(compareFn: CompareFunction<T, 'number'>, leftBound: T, rightBound: T);
constructor(
compareFn: CompareFunction<T, any> = (a: any, b: any) => a - b,
leftBound: any = -Infinity,
rightBound: any = Infinity,
) {
this.root = new TreapNode<T>(rightBound);
this.root.priority = Infinity;
this.root.left = new TreapNode<T>(leftBound);
this.root.left.priority = -Infinity;
this.root.pushUp();
this.leftBound = leftBound;
this.rightBound = rightBound;
this.compareFn = compareFn;
}
get size(): number {
return this.root.size - 2;
}
get height(): number {
const getHeight = (node: TreapNode<T> | null): number => {
if (node == null) return 0;
return 1 + Math.max(getHeight(node.left), getHeight(node.right));
};
return getHeight(this.root);
}
/**
*
* @complexity `O(logn)`
* @description Returns true if value is a member.
*/
has(value: T): boolean {
const compare = this.compareFn;
const dfs = (node: TreapNode<T> | null, value: T): boolean => {
if (node == null) return false;
if (compare(node.value, value) === 0) return true;
if (compare(node.value, value) < 0) return dfs(node.right, value);
return dfs(node.left, value);
};
return dfs(this.root, value);
}
/**
*
* @complexity `O(logn)`
* @description Add value to sorted set.
*/
add(...values: T[]): this {
const compare = this.compareFn;
const dfs = (
node: TreapNode<T> | null,
value: T,
parent: TreapNode<T>,
direction: 'left' | 'right',
): void => {
if (node == null) return;
if (compare(node.value, value) === 0) {
node.count++;
node.pushUp();
} else if (compare(node.value, value) > 0) {
if (node.left) {
dfs(node.left, value, node, 'left');
} else {
node.left = new TreapNode(value);
node.pushUp();
}
if (TreapNode.getFac(node.left) > node.priority) {
parent[direction] = node.rotateRight();
}
} else if (compare(node.value, value) < 0) {
if (node.right) {
dfs(node.right, value, node, 'right');
} else {
node.right = new TreapNode(value);
node.pushUp();
}
if (TreapNode.getFac(node.right) > node.priority) {
parent[direction] = node.rotateLeft();
}
}
parent.pushUp();
};
values.forEach(value => dfs(this.root.left, value, this.root, 'left'));
return this;
}
/**
*
* @complexity `O(logn)`
* @description Remove value from sorted set if it is a member.
* If value is not a member, do nothing.
*/
delete(value: T): void {
const compare = this.compareFn;
const dfs = (
node: TreapNode<T> | null,
value: T,
parent: TreapNode<T>,
direction: 'left' | 'right',
): void => {
if (node == null) return;
if (compare(node.value, value) === 0) {
if (node.count > 1) {
node.count--;
node?.pushUp();
} else if (node.left == null && node.right == null) {
parent[direction] = null;
} else {
// 旋到根节点
if (
node.right == null ||
TreapNode.getFac(node.left) > TreapNode.getFac(node.right)
) {
parent[direction] = node.rotateRight();
dfs(parent[direction]?.right ?? null, value, parent[direction]!, 'right');
} else {
parent[direction] = node.rotateLeft();
dfs(parent[direction]?.left ?? null, value, parent[direction]!, 'left');
}
}
} else if (compare(node.value, value) > 0) {
dfs(node.left, value, node, 'left');
} else if (compare(node.value, value) < 0) {
dfs(node.right, value, node, 'right');
}
parent?.pushUp();
};
dfs(this.root.left, value, this.root, 'left');
}
/**
*
* @complexity `O(logn)`
* @description Returns an index to insert value in the sorted set.
* If the value is already present, the insertion point will be before (to the left of) any existing values.
*/
bisectLeft(value: T): number {
const compare = this.compareFn;
const dfs = (node: TreapNode<T> | null, value: T): number => {
if (node == null) return 0;
if (compare(node.value, value) === 0) {
return TreapNode.getSize(node.left);
} else if (compare(node.value, value) > 0) {
return dfs(node.left, value);
} else if (compare(node.value, value) < 0) {
return dfs(node.right, value) + TreapNode.getSize(node.left) + node.count;
}
return 0;
};
return dfs(this.root, value) - 1;
}
/**
*
* @complexity `O(logn)`
* @description Returns an index to insert value in the sorted set.
* If the value is already present, the insertion point will be before (to the right of) any existing values.
*/
bisectRight(value: T): number {
const compare = this.compareFn;
const dfs = (node: TreapNode<T> | null, value: T): number => {
if (node == null) return 0;
if (compare(node.value, value) === 0) {
return TreapNode.getSize(node.left) + node.count;
} else if (compare(node.value, value) > 0) {
return dfs(node.left, value);
} else if (compare(node.value, value) < 0) {
return dfs(node.right, value) + TreapNode.getSize(node.left) + node.count;
}
return 0;
};
return dfs(this.root, value) - 1;
}
/**
*
* @complexity `O(logn)`
* @description Returns the index of the first occurrence of a value in the set, or -1 if it is not present.
*/
indexOf(value: T): number {
const compare = this.compareFn;
let isExist = false;
const dfs = (node: TreapNode<T> | null, value: T): number => {
if (node == null) return 0;
if (compare(node.value, value) === 0) {
isExist = true;
return TreapNode.getSize(node.left);
} else if (compare(node.value, value) > 0) {
return dfs(node.left, value);
} else if (compare(node.value, value) < 0) {
return dfs(node.right, value) + TreapNode.getSize(node.left) + node.count;
}
return 0;
};
const res = dfs(this.root, value) - 1;
return isExist ? res : -1;
}
/**
*
* @complexity `O(logn)`
* @description Returns the index of the last occurrence of a value in the set, or -1 if it is not present.
*/
lastIndexOf(value: T): number {
const compare = this.compareFn;
let isExist = false;
const dfs = (node: TreapNode<T> | null, value: T): number => {
if (node == null) return 0;
if (compare(node.value, value) === 0) {
isExist = true;
return TreapNode.getSize(node.left) + node.count - 1;
} else if (compare(node.value, value) > 0) {
return dfs(node.left, value);
} else if (compare(node.value, value) < 0) {
return dfs(node.right, value) + TreapNode.getSize(node.left) + node.count;
}
return 0;
};
const res = dfs(this.root, value) - 1;
return isExist ? res : -1;
}
/**
*
* @complexity `O(logn)`
* @description Returns the item located at the specified index.
* @param index The zero-based index of the desired code unit. A negative index will count back from the last item.
*/
at(index: number): T | undefined {
if (index < 0) index += this.size;
if (index < 0 || index >= this.size) return undefined;
const dfs = (node: TreapNode<T> | null, rank: number): T | undefined => {
if (node == null) return undefined;
if (TreapNode.getSize(node.left) >= rank) {
return dfs(node.left, rank);
} else if (TreapNode.getSize(node.left) + node.count >= rank) {
return node.value;
} else {
return dfs(node.right, rank - TreapNode.getSize(node.left) - node.count);
}
};
const res = dfs(this.root, index + 2);
return ([this.leftBound, this.rightBound] as any[]).includes(res) ? undefined : res;
}
/**
*
* @complexity `O(logn)`
* @description Find and return the element less than `val`, return `undefined` if no such element found.
*/
lower(value: T): T | undefined {
const compare = this.compareFn;
const dfs = (node: TreapNode<T> | null, value: T): T | undefined => {
if (node == null) return undefined;
if (compare(node.value, value) >= 0) return dfs(node.left, value);
const tmp = dfs(node.right, value);
if (tmp == null || compare(node.value, tmp) > 0) {
return node.value;
} else {
return tmp;
}
};
const res = dfs(this.root, value) as any;
return res === this.leftBound ? undefined : res;
}
/**
*
* @complexity `O(logn)`
* @description Find and return the element greater than `val`, return `undefined` if no such element found.
*/
higher(value: T): T | undefined {
const compare = this.compareFn;
const dfs = (node: TreapNode<T> | null, value: T): T | undefined => {
if (node == null) return undefined;
if (compare(node.value, value) <= 0) return dfs(node.right, value);
const tmp = dfs(node.left, value);
if (tmp == null || compare(node.value, tmp) < 0) {
return node.value;
} else {
return tmp;
}
};
const res = dfs(this.root, value) as any;
return res === this.rightBound ? undefined : res;
}
/**
*
* @complexity `O(logn)`
* @description Find and return the element less than or equal to `val`, return `undefined` if no such element found.
*/
floor(value: T): T | undefined {
const compare = this.compareFn;
const dfs = (node: TreapNode<T> | null, value: T): T | undefined => {
if (node == null) return undefined;
if (compare(node.value, value) === 0) return node.value;
if (compare(node.value, value) >= 0) return dfs(node.left, value);
const tmp = dfs(node.right, value);
if (tmp == null || compare(node.value, tmp) > 0) {
return node.value;
} else {
return tmp;
}
};
const res = dfs(this.root, value) as any;
return res === this.leftBound ? undefined : res;
}
/**
*
* @complexity `O(logn)`
* @description Find and return the element greater than or equal to `val`, return `undefined` if no such element found.
*/
ceil(value: T): T | undefined {
const compare = this.compareFn;
const dfs = (node: TreapNode<T> | null, value: T): T | undefined => {
if (node == null) return undefined;
if (compare(node.value, value) === 0) return node.value;
if (compare(node.value, value) <= 0) return dfs(node.right, value);
const tmp = dfs(node.left, value);
if (tmp == null || compare(node.value, tmp) < 0) {
return node.value;
} else {
return tmp;
}
};
const res = dfs(this.root, value) as any;
return res === this.rightBound ? undefined : res;
}
/**
* @complexity `O(logn)`
* @description
* Returns the last element from set.
* If the set is empty, undefined is returned.
*/
first(): T | undefined {
const iter = this.inOrder();
iter.next();
const res = iter.next().value;
return res === this.rightBound ? undefined : res;
}
/**
* @complexity `O(logn)`
* @description
* Returns the last element from set.
* If the set is empty, undefined is returned .
*/
last(): T | undefined {
const iter = this.reverseInOrder();
iter.next();
const res = iter.next().value;
return res === this.leftBound ? undefined : res;
}
/**
* @complexity `O(logn)`
* @description
* Removes the first element from an set and returns it.
* If the set is empty, undefined is returned and the set is not modified.
*/
shift(): T | undefined {
const first = this.first();
if (first === undefined) return undefined;
this.delete(first);
return first;
}
/**
* @complexity `O(logn)`
* @description
* Removes the last element from an set and returns it.
* If the set is empty, undefined is returned and the set is not modified.
*/
pop(index?: number): T | undefined {
if (index == null) {
const last = this.last();
if (last === undefined) return undefined;
this.delete(last);
return last;
}
const toDelete = this.at(index);
if (toDelete == null) return;
this.delete(toDelete);
return toDelete;
}
/**
*
* @complexity `O(logn)`
* @description
* Returns number of occurrences of value in the sorted set.
*/
count(value: T): number {
const compare = this.compareFn;
const dfs = (node: TreapNode<T> | null, value: T): number => {
if (node == null) return 0;
if (compare(node.value, value) === 0) return node.count;
if (compare(node.value, value) < 0) return dfs(node.right, value);
return dfs(node.left, value);
};
return dfs(this.root, value);
}
*[Symbol.iterator](): Generator<T, any, any> {
yield* this.values();
}
/**
* @description
* Returns an iterable of keys in the set.
*/
*keys(): Generator<T, any, any> {
yield* this.values();
}
/**
* @description
* Returns an iterable of values in the set.
*/
*values(): Generator<T, any, any> {
const iter = this.inOrder();
iter.next();
const steps = this.size;
for (let _ = 0; _ < steps; _++) {
yield iter.next().value;
}
}
/**
* @description
* Returns a generator for reversed order traversing the set.
*/
*rvalues(): Generator<T, any, any> {
const iter = this.reverseInOrder();
iter.next();
const steps = this.size;
for (let _ = 0; _ < steps; _++) {
yield iter.next().value;
}
}
/**
* @description
* Returns an iterable of key, value pairs for every entry in the set.
*/
*entries(): IterableIterator<[number, T]> {
const iter = this.inOrder();
iter.next();
const steps = this.size;
for (let i = 0; i < steps; i++) {
yield [i, iter.next().value];
}
}
private *inOrder(root: TreapNode<T> | null = this.root): Generator<T, any, any> {
if (root == null) return;
yield* this.inOrder(root.left);
const count = root.count;
for (let _ = 0; _ < count; _++) {
yield root.value;
}
yield* this.inOrder(root.right);
}
private *reverseInOrder(root: TreapNode<T> | null = this.root): Generator<T, any, any> {
if (root == null) return;
yield* this.reverseInOrder(root.right);
const count = root.count;
for (let _ = 0; _ < count; _++) {
yield root.value;
}
yield* this.reverseInOrder(root.left);
}
}func longestSubarray(nums []int, limit int) (ans int) {
merge := func(st *redblacktree.Tree[int, int], x, v int) {
c, _ := st.Get(x)
if c+v == 0 {
st.Remove(x)
} else {
st.Put(x, c+v)
}
}
st := redblacktree.New[int, int]()
j := 0
for i, x := range nums {
merge(st, x, 1)
for ; st.Right().Key-st.Left().Key > limit; j++ {
merge(st, nums[j], -1)
}
ans = max(ans, i-j+1)
}
return
}Approach 4: Binary Search + Sliding Window
We notice that if a subarray of length k satisfies the condition, then a subarray of length k' < k also satisfies the condition. This shows a monotonicity, therefore, we can use binary search to find the longest subarray that satisfies the condition.
We define the left boundary of the binary search as l = 0, and the right boundary as r = n. For each mid = \frac{l + r + 1}{2}, we check whether there exists a subarray of length mid that satisfies the condition. If it exists, we update l = mid, otherwise we update r = mid - 1. The problem is transformed into whether there exists a subarray of length mid in the array that satisfies the condition, which is actually to find the difference between the maximum and minimum values in the sliding window does not exceed limit. We can use two monotonic queues to maintain the maximum and minimum values in the window respectively.
The time complexity is O(n times log n), and the space complexity is O(n). Where n is the length of the array nums.
function longestSubarray(nums: number[], limit: number): number {
const n = nums.length;
let [l, r] = [0, n];
const check = (k: number): boolean => {
const minq = new Deque<number>();
const maxq = new Deque<number>();
for (let i = 0; i < n; ++i) {
while (!minq.isEmpty() && i - minq.frontValue()! + 1 > k) {
minq.popFront();
}
while (!maxq.isEmpty() && i - maxq.frontValue()! + 1 > k) {
maxq.popFront();
}
while (!minq.isEmpty() && nums[minq.backValue()!] >= nums[i]) {
minq.popBack();
}
while (!maxq.isEmpty() && nums[maxq.backValue()!] <= nums[i]) {
maxq.popBack();
}
minq.pushBack(i);
maxq.pushBack(i);
if (i >= k - 1 && nums[maxq.frontValue()!] - nums[minq.frontValue()!] <= limit) {
return true;
}
}
return false;
};
while (l < r) {
const mid = (l + r + 1) >> 1;
if (check(mid)) {
l = mid;
} else {
r = mid - 1;
}
}
return l;
}
class Node<T> {
value: T;
next: Node<T> | null;
prev: Node<T> | null;
constructor(value: T) {
this.value = value;
this.next = null;
this.prev = null;
}
}
class Deque<T> {
private front: Node<T> | null;
private back: Node<T> | null;
private size: number;
constructor() {
this.front = null;
this.back = null;
this.size = 0;
}
pushFront(val: T): void {
const newNode = new Node(val);
if (this.isEmpty()) {
this.front = newNode;
this.back = newNode;
} else {
newNode.next = this.front;
this.front!.prev = newNode;
this.front = newNode;
}
this.size++;
}
pushBack(val: T): void {
const newNode = new Node(val);
if (this.isEmpty()) {
this.front = newNode;
this.back = newNode;
} else {
newNode.prev = this.back;
this.back!.next = newNode;
this.back = newNode;
}
this.size++;
}
popFront(): T | undefined {
if (this.isEmpty()) {
return undefined;
}
const value = this.front!.value;
this.front = this.front!.next;
if (this.front !== null) {
this.front.prev = null;
} else {
this.back = null;
}
this.size--;
return value;
}
popBack(): T | undefined {
if (this.isEmpty()) {
return undefined;
}
const value = this.back!.value;
this.back = this.back!.prev;
if (this.back !== null) {
this.back.next = null;
} else {
this.front = null;
}
this.size--;
return value;
}
frontValue(): T | undefined {
return this.front?.value;
}
backValue(): T | undefined {
return this.back?.value;
}
getSize(): number {
return this.size;
}
isEmpty(): boolean {
return this.size === 0;
}
}func longestSubarray(nums []int, limit int) int {
l, r := 0, len(nums)
check := func(k int) bool {
minq := Deque{}
maxq := Deque{}
for i, x := range nums {
for !minq.Empty() && i-minq.Front()+1 > k {
minq.PopFront()
}
for !maxq.Empty() && i-maxq.Front()+1 > k {
maxq.PopFront()
}
for !minq.Empty() && nums[minq.Back()] >= x {
minq.PopBack()
}
for !maxq.Empty() && nums[maxq.Back()] <= x {
maxq.PopBack()
}
minq.PushBack(i)
maxq.PushBack(i)
if i >= k-1 && nums[maxq.Front()]-nums[minq.Front()] <= limit {
return true
}
}
return false
}
for l < r {
mid := (l + r + 1) >> 1
if check(mid) {
l = mid
} else {
r = mid - 1
}
}
return l
}
// template
type Deque struct{ l, r []int }
func (q Deque) Empty() bool {
return len(q.l) == 0 && len(q.r) == 0
}
func (q Deque) Size() int {
return len(q.l) + len(q.r)
}
func (q *Deque) PushFront(v int) {
q.l = append(q.l, v)
}
func (q *Deque) PushBack(v int) {
q.r = append(q.r, v)
}
func (q *Deque) PopFront() (v int) {
if len(q.l) > 0 {
q.l, v = q.l[:len(q.l)-1], q.l[len(q.l)-1]
} else {
v, q.r = q.r[0], q.r[1:]
}
return
}
func (q *Deque) PopBack() (v int) {
if len(q.r) > 0 {
q.r, v = q.r[:len(q.r)-1], q.r[len(q.r)-1]
} else {
v, q.l = q.l[0], q.l[1:]
}
return
}
func (q Deque) Front() int {
if len(q.l) > 0 {
return q.l[len(q.l)-1]
}
return q.r[0]
}
func (q Deque) Back() int {
if len(q.r) > 0 {
return q.r[len(q.r)-1]
}
return q.l[0]
}
func (q Deque) Get(i int) int {
if i < len(q.l) {
return q.l[len(q.l)-1-i]
}
return q.r[i-len(q.l)]
}Approach 5: Sliding Window + Deque
We can use a deque to maintain the maximum and minimum values within the window. We maintain two deques, one for storing the indices of the maximum values and the other for the minimum values within the window. Define two pointers l and r to point to the left and right boundaries of the window, respectively.
Each time we move the right boundary r to the right, we check if the element corresponding to the tail index of the maximum value deque is less than the current element. If it is, we dequeue the tail element until the element corresponding to the tail of the maximum value deque is not less than the current element. Similarly, we check if the element corresponding to the tail index of the minimum value deque is greater than the current element. If it is, we dequeue the tail element until the element corresponding to the tail of the minimum value deque is not greater than the current element. Then, we enqueue the current element's index.
If the difference between the elements at the front of the maximum value deque and the minimum value deque is greater than limit, then we move the left boundary l to the right. If the element at the front of the maximum value deque is less than l, we dequeue the front element of the maximum value deque. Similarly, if the element at the front of the minimum value deque is less than l, we dequeue the front element of the minimum value deque.
The answer is n - l.
The time complexity is O(n), and the space complexity is O(n). Here, n is the length of the array nums.
function longestSubarray(nums: number[], limit: number): number {
const n = nums.length;
let [h1, t1] = [0, -1];
let [h2, t2] = [0, -1];
let l = 0;
const maxq = Array(n);
const minq = Array(n);
for (let r = 0; r < n; ++r) {
while (h1 <= t1 && nums[maxq[t1]] < nums[r]) {
--t1;
}
while (h2 <= t2 && nums[minq[t2]] > nums[r]) {
--t2;
}
maxq[++t1] = r;
minq[++t2] = r;
if (nums[maxq[h1]] - nums[minq[h2]] > limit) {
++l;
if (maxq[h1] < l) {
++h1;
}
if (minq[h2] < l) {
++h2;
}
}
}
return n - l;
}func longestSubarray(nums []int, limit int) int {
var maxq, minq Deque
n := len(nums)
l := 0
for r, x := range nums {
for !maxq.Empty() && nums[maxq.Back()] < x {
maxq.PopBack()
}
for !minq.Empty() && nums[minq.Back()] > x {
minq.PopBack()
}
maxq.PushBack(r)
minq.PushBack(r)
if nums[maxq.Front()]-nums[minq.Front()] > limit {
l++
if maxq.Front() < l {
maxq.PopFront()
}
if minq.Front() < l {
minq.PopFront()
}
}
}
return n - l
}
type Deque struct{ l, r []int }
func (q Deque) Empty() bool {
return len(q.l) == 0 && len(q.r) == 0
}
func (q Deque) Size() int {
return len(q.l) + len(q.r)
}
func (q *Deque) PushFront(v int) {
q.l = append(q.l, v)
}
func (q *Deque) PushBack(v int) {
q.r = append(q.r, v)
}
func (q *Deque) PopFront() (v int) {
if len(q.l) > 0 {
q.l, v = q.l[:len(q.l)-1], q.l[len(q.l)-1]
} else {
v, q.r = q.r[0], q.r[1:]
}
return
}
func (q *Deque) PopBack() (v int) {
if len(q.r) > 0 {
q.r, v = q.r[:len(q.r)-1], q.r[len(q.r)-1]
} else {
v, q.l = q.l[0], q.l[1:]
}
return
}
func (q Deque) Front() int {
if len(q.l) > 0 {
return q.l[len(q.l)-1]
}
return q.r[0]
}
func (q Deque) Back() int {
if len(q.r) > 0 {
return q.r[len(q.r)-1]
}
return q.l[0]
}
func (q Deque) Get(i int) int {
if i < len(q.l) {
return q.l[len(q.l)-1-i]
}
return q.r[i-len(q.l)]
}Complexity comparison
| Approach | Complexity |
|---|---|
| Sliding Window with Deques | Time Complexity: O(n), where n is the length of the array, because each element is added and removed from the deque at most once. Space Complexity: O(n), due to the size of the deques which in the worst case can hold all elements. |
| Sliding Window with TreeMap (Java) | Time Complexity: O(n log n); each insertion and deletion operation on TreeMap takes log n time. Space Complexity: O(n); due to the additional space required by the TreeMap. |
| Ordered Set + Sliding Window | – |
| Binary Search + Sliding Window | – |
| Sliding Window + Deque | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Sliding Window with Monotonic Deques | O(n) | O(n) | Best overall solution when you need constant-time min and max tracking inside a sliding window |
| Sliding Window with TreeMap | O(n log n) | O(n) | Simpler to implement when the language provides ordered maps or sets |
Frequently asked questions
Is Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit easy or hard?
The problem is generally rated Medium difficulty. The sliding window idea is straightforward, but efficiently maintaining both the minimum and maximum inside the window requires understanding monotonic queues, which many candidates encounter for the first time in this type of problem.
Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit Python/Java solution
Most implementations use the sliding window with monotonic deque pattern. Python uses collections.deque, Java typically uses ArrayDeque, and C++ uses deque from the STL. The algorithm maintains decreasing and increasing deques to track the current window's maximum and minimum values.
How to solve Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit in O(n)?
Use a sliding window with two deques: one decreasing deque for maximum values and one increasing deque for minimum values. Expand the right pointer while inserting elements into both structures. If the difference between the deque fronts exceeds the limit, move the left pointer and remove outdated elements. This keeps max and min queries O(1), producing an overall O(n) algorithm.
What is the best approach for Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit?
The optimal approach uses a sliding window with two monotonic deques to maintain the current maximum and minimum values. This allows checking the constraint max − min ≤ limit in constant time while expanding and shrinking the window. Each element is processed once, resulting in O(n) time complexity and O(n) space.
Is Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit asked at Google/Amazon/Meta?
Sliding window problems with min/max constraints appear frequently in interviews at companies such as Amazon, Google, and Meta. Variants of this problem test your ability to maintain dynamic window statistics using data structures like monotonic queues or ordered maps.
What data structure is used in Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit?
The most efficient solution uses two monotonic deques to track the maximum and minimum values within the sliding window. Another common implementation uses an ordered map such as TreeMap to keep elements sorted and retrieve the smallest and largest values quickly.
What is the time complexity of Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit?
The optimal monotonic deque solution runs in O(n) time because every element is added and removed from the deques at most once. Space complexity is O(n) in the worst case for the deques. An alternative TreeMap solution takes O(n log n) time due to ordered map insertions and deletions.
