"""Interval overlap detection via sweep-line. Given a list of half-open intervals [start, end), returns all unordered pairs (i, j) with i < j whose intervals intersect (share at least one point). Half-open semantics: [1,2) and [2,3) do NOT overlap. Complexity of `find_overlapping_pairs` is O(n log n + k), where n is the number of intervals and k is the number of overlapping pairs it returns (k can be Theta(n^2), so the sort bound alone does not describe it). `has_any_overlap` answers the boolean question in O(n log n) and stops at the first overlapping pair, without building the pair list. """ from __future__ import annotations from typing import Iterable, List, Tuple Interval = Tuple[int, int] def find_overlapping_pairs(intervals: Iterable[Interval]) -> List[Tuple[int, int]]: """Return sorted list of (i, j) index pairs (i= e: # zero-length interval: empty set, never overlaps continue for other in active: a, b = (idx, other) if idx < other else (other, idx) pairs.add((a, b)) active.add(idx) else: # end: remove active.discard(idx) return sorted(pairs) def has_any_overlap(intervals: Iterable[Interval]) -> bool: """True if any two half-open intervals overlap; stops at the first pair. O(n log n) time, O(n) space: it never materialises the pair list, so a fully-overlapping input costs one comparison rather than n*(n-1)/2 pairs. """ iv = list(intervals) for s, e in iv: if e < s: raise ValueError(f"end < start: {(s, e)}") # Sorted by start, every earlier start is <= the current one, so an # overlap exists iff some earlier non-empty interval ends after this # start. Tracking the running maximum end is enough. max_end = None for s, e in sorted(iv): if s >= e: # zero-length interval: empty set, never overlaps continue if max_end is not None and s < max_end: return True if max_end is None or e > max_end: max_end = e return False def brute_force_pairs(intervals: Iterable[Interval]) -> List[Tuple[int, int]]: iv = list(intervals) n = len(iv) out = [] for i in range(n): for j in range(i + 1, n): s1, e1 = iv[i] s2, e2 = iv[j] # half-open overlap: max(starts) < min(ends) if max(s1, s2) < min(e1, e2): out.append((i, j)) return out if __name__ == "__main__": import random random.seed(42) for trial in range(2000): n = random.randint(0, 12) ivs = [] for _ in range(n): a = random.randint(-5, 5) b = a + random.randint(0, 6) ivs.append((a, b)) got = find_overlapping_pairs(ivs) exp = brute_force_pairs(ivs) if got != exp: print("MISMATCH", ivs, got, exp) raise SystemExit(1) print("all 2000 random trials passed") # edge cases assert find_overlapping_pairs([(1, 2), (2, 3)]) == [] assert find_overlapping_pairs([(1, 3), (2, 4)]) == [(0, 1)] assert find_overlapping_pairs([]) == [] assert find_overlapping_pairs([(5, 5)]) == [] # zero-length never overlaps print("edge cases passed")