Ordered Set (TreeMap)
Keep a dynamic set of keys in sorted order (a balanced BST such as Java's TreeMap) so you can insert, delete, and query floor, ceiling, min, max and ranges in O(log n) as the data changes.
Instinct Keys keep arriving or leaving and you must ask "what is the nearest key below/above x?" or "current min/max" each time - reach for TreeMap/TreeSet.
Memory hack A HashMap with a sense of direction - floor looks down, ceiling looks up, and both answer in log time while the data keeps changing.
At a glance
When to use it
- Keys are inserted and removed between queries
- You need the nearest key at or below (or above) a value while the data changes
- You need the live min or max even though arbitrary items get deleted or corrected
- A new booking must be rejected if it overlaps an existing one
Key idea
A balanced BST such as TreeMap keeps keys sorted at all times, so insert, delete, floor, ceiling, min and max each cost O(log n) instead of re-sorting or shifting a sorted array in O(n) per update.
Typical complexity
O(log n) time per operation, O(n) space
| # | Common problems | Level | Our solution | Practice |
|---|---|---|---|---|
| 1 | My Calendar I | Medium | Read solution | LeetCode ↗ |
| 2 | My Calendar II | Medium | Read solution | LeetCode ↗ |
| 3 | Stock Price Fluctuation | Medium | Read solution | LeetCode ↗ |
| 4 | Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit | Medium | Read solution | LeetCode ↗ |
| 5 | Sliding Window Median | Hard | Read solution | LeetCode ↗ |
The instinct
Sorting once is enough when data is static. When keys are inserted and removed between queries, re-sorting every time is O(n log n) per operation and binary search on an ArrayList still costs O(n) per insert. A self-balancing BST - Java's TreeMap and TreeSet (red-black trees) - keeps keys sorted at all times and answers "nearest neighbour" questions in O(log n):
floorKey(x)- greatest key<= x;lowerKey(x)- strictly< xceilingKey(x)- smallest key>= x;higherKey(x)- strictly> xfirstKey()/lastKey()- current min / max;pollFirstEntry()removes itheadMap,tailMap,subMap- range views
Think of it as a hash map that also knows which keys are next to each other.
Recognise it
| Signal in the problem | Why it points here |
|---|---|
| "Book / reserve if it does not overlap existing bookings" | Only the neighbouring booking on each side can clash - floor and ceiling |
| "Closest value to x among those seen so far" | Floor and ceiling of x, take the nearer one |
| "Current max / min after updates and deletions" | A heap cannot delete arbitrary items cheaply; a TreeMap can |
| "Prices get corrected" / "a record's value changes later" | Remove old value, insert new one, read lastKey() - a sorted multiset |
| "At most k overlapping bookings" / "max concurrent at any time" online | Difference map: +1 at start, -1 at end, walk keys in order |
| "Count / sum of keys in range [lo, hi]" with updates (small n) | subMap view, or upgrade to a Fenwick/segment tree for heavy use |
| Sliding window that needs min and max with duplicates | TreeMap of value to count |
Not this pattern if...
- All data is known up front - sort once and use two pointers or binary search.
- You only ever need the single min or max and never delete arbitrary elements - a heap is simpler and faster.
- You need "kth smallest" or "how many keys less than x" quickly -
TreeMapcannot rank in O(log n); use a Fenwick tree, or an order-statistic tree in other languages.
Memory hack
"A HashMap with a sense of direction." Floor looks down, ceiling looks up. If your brute force says "scan the sorted list for the neighbour of x", replace the scan with floorKey/ceilingKey.
Analogy - a parking street with numbered spots. When a new car wants to park between spots, you do not inspect every car on the street; you only glance at the car immediately in front and immediately behind. A TreeMap is that glance - it gives you the two neighbours instantly, even as cars come and go.
Say it out loud in the interview:
- "Data changes between queries, so I keep keys in a TreeMap - O(log n) insert, delete and neighbour lookup."
- "The only candidates that matter are
floor(x)andceiling(x)- let me check both and handlenull." - "If values repeat, I store value to count, and remove the key when the count hits zero."
How it works
For My Calendar I, store bookings as start -> end. A new half-open booking [s, e) can only collide with the booking that starts just before it (floor of s) or just after it (ceiling of s). Two O(log n) lookups replace a scan of every booking.
flowchart TD
A["book(s, e)"] --> B["prev = floorEntry(s)"]
B --> C{"prev != null and prev.end > s?"}
C -->|yes| X["reject"]
C -->|no| D["next = ceilingEntry(s)"]
D --> E{"next != null and next.start < e?"}
E -->|yes| X
E -->|no| F["put(s, e) and accept"]
Trace with bookings [10,20), [15,25), [20,30):
| Request | floor(s) | ceiling(s) | Clash? | Map after |
|---|---|---|---|---|
| [10,20) | none | none | no | {10:20} |
| [15,25) | 10:20, ends 20 > 15 | - | yes | {10:20} |
| [20,30) | 10:20, ends 20 = 20 | none | no | {10:20, 20:30} |
Java template
import java.util.*; class OrderedSetTemplates { // 1) Neighbour check: insert a half-open range only if it overlaps nothing. private final TreeMap<Integer, Integer> booked = new TreeMap<>(); boolean book(int start, int end) { Map.Entry<Integer, Integer> prev = booked.floorEntry(start); if (prev != null && prev.getValue() > start) return false; // left neighbour overlaps Map.Entry<Integer, Integer> next = booked.ceilingEntry(start); if (next != null && next.getKey() < end) return false; // right neighbour overlaps booked.put(start, end); return true; } // 2) Difference map: max overlap online (use for "at most k bookings"). private final TreeMap<Integer, Integer> delta = new TreeMap<>(); boolean bookAtMostK(int start, int end, int k) { delta.merge(start, 1, Integer::sum); delta.merge(end, -1, Integer::sum); int active = 0; for (int d : delta.values()) { // keys iterate in sorted order active += d; if (active > k) { // roll back and reject delta.merge(start, -1, Integer::sum); delta.merge(end, 1, Integer::sum); return false; } } return true; } // 3) Sorted multiset: value -> count, supports remove-one and live min/max. private final TreeMap<Integer, Integer> bag = new TreeMap<>(); void add(int v) { bag.merge(v, 1, Integer::sum); } void removeOne(int v) { Integer c = bag.get(v); if (c == null) return; if (c == 1) bag.remove(v); else bag.put(v, c - 1); } int max() { return bag.lastKey(); } // throws if empty - guard in real code int min() { return bag.firstKey(); } Integer closestAtMost(int x) { return bag.floorKey(x); } // null when nothing <= x }
Complexity
| Operation | TreeMap / TreeSet | Comparison |
|---|---|---|
| put / remove / containsKey | O(log n) | HashMap O(1) average, but unordered |
| floor / ceiling / lower / higher | O(log n) | Sorted array: O(log n) query but O(n) insert |
| first / last / pollFirst | O(log n) | Heap: O(1) peek, but O(n) arbitrary delete |
| Iterate all keys in order | O(n) | - |
| Space | O(n) | - |
The difference-map walk in template 2 is O(n) per booking, which is fine for the small limits in My Calendar II; a segment tree removes that cost for large inputs.
Variations
- Neighbour conflict check - floor + ceiling around the new key. My Calendar I, nearest seat/parking spot, "contains duplicate within value distance t".
- Difference map / online sweep - counts at sorted boundaries. My Calendar II (at most double booking), car pooling with updates.
- Sorted multiset - value to count, live min/max under corrections. Stock Price Fluctuation (timestamp map + price multiset, or a heap with lazy deletion).
- Interval map merging - store disjoint ranges keyed by start; merge with floor and neighbours on insert (range module, summary ranges from a stream).
- Ordered pruning on a hand-built BST - the same "outside the range, skip that side" thinking applies to Trim a Binary Search Tree.
- Sliding window with order - window min/max/median with deletions via a counted TreeMap.
Common mistakes
floorKey, ceilingKey, floorEntry and friends return null when no such key exists. Unboxing that null into an int throws a NullPointerException - always store the result in an Integer/Map.Entry and check it.
- Mixing up inclusive and strict versions:
flooris<=,loweris<;ceilingis>=,higheris>. - Using a
TreeSetwhen values can repeat - duplicates silently disappear. Use a TreeMap of counts. - Forgetting to remove a key when its count drops to zero, so
firstKey()/lastKey()report values that no longer exist. - Modifying the map while iterating over
keySet()orvalues()- collect changes first or use the iterator'sremove(). - Reaching for a TreeMap when a heap suffices, or claiming O(log n) "kth smallest" - Java's TreeMap has no rank query.
All practice problems
4 problems on this site use this pattern, easiest first. Solve them in order and say the instinct out loud before you code.