Approach (O(n)): maintain two monotonic deques to track the sliding-window minimum and maximum so you can check in O(1) whether max - min <= epsilon for any window. Scan once with a right pointer; when window length >= L and condition holds, record a plateau start at right-L+1 and optionally extend the window while condition remains true. This is linear because each element is pushed/popped at most once from each deque.python
from collections import deque
import math
def detect_plateaus(arr, eps, L):
"""
Returns list of (start, end) indices (inclusive) of plateau regions where
for every index i..j the values remain within eps and length >= L.
O(n) time, O(L) worst-case auxiliary (deques).
"""
n = len(arr)
if L <= 0 or n < L:
return []
min_q = deque() # stores indices, arr[idx] increasing
max_q = deque() # stores indices, arr[idx] decreasing
plateaus = []
left = 0
for right, v in enumerate(arr):
# ignore NaN/inf: treat them as breakers
if not (math.isfinite(v)):
min_q.clear(); max_q.clear(); left = right + 1
continue
# maintain min deque (increasing)
while min_q and arr[min_q[-1]] >= v:
min_q.pop()
min_q.append(right)
# maintain max deque (decreasing)
while max_q and arr[max_q[-1]] <= v:
max_q.pop()
max_q.append(right)
# shrink left until window satisfies numeric validity (we keep left as is;
# but we need to ensure left points to earliest index in current valid block)
# Slide left forward only if left beyond deque heads
while left <= right and (arr[max_q[0]] - arr[min_q[0]] > eps):
# advance left to remove influence
if min_q and min_q[0] == left:
min_q.popleft()
if max_q and max_q[0] == left:
max_q.popleft()
left += 1
# if current window length >= L, record plateau(s)
if right - left + 1 >= L:
# record a plateau starting at left and ending at right.
# To avoid duplicates when we slide right, only append when right expands.
plateaus.append((left, right))
# optional: to return maximal disjoint intervals, you can merge/extend later
return merge_intervals(plateaus)
def merge_intervals(intervals):
if not intervals:
return []
intervals.sort()
merged = [list(intervals[0])]
for s,e in intervals[1:]:
if s <= merged[-1][1] + 1:
merged[-1][1] = max(merged[-1][1], e)
else:
merged.append([s,e])
return [(a,b) for a,b in merged]
Key concepts:- Monotonic deques support O(1) min/max in a sliding window; each index enqueued/dequeued at most once.- Use absolute or relative epsilon: use abs(max-min) <= eps for absolute; for values spanning magnitudes use relative: (max-min) <= eps * max(1, |mean|).- Handle NaN/Inf as boundaries that break plateaus.Numerical stability & noise tolerance:- Floating noise can create tiny fluctuations; choose eps >= typical numeric noise or pre-smooth (low-pass, moving average, median filter) before detection.- Use fused operations and avoid subtracting nearly equal large numbers; when using relative tolerance compare normalized difference (e.g., (max-min)/max_abs).- Consider hysteresis: require condition hold for L and allow up to k intermittent violations (tolerance window) to reduce false negatives.Complexity: O(n) time, O(L) worst-case space for deques.Edge cases: constant arrays, sequences with NaN/Inf, varying scale — choose absolute vs relative eps accordingly.