Operation matrices
Operation-by-operation cost tables for arrays, sorted arrays, linked lists, deques, hash tables, balanced trees and heaps: index access, insert, delete, search, min/max, predecessor/successor and range queries.
Learning goals
- Read the cost of each operation for the common structures
- Match a problem's dominant operations to the structure that makes them cheap
- Recognise which operations a structure does not support at all
Prerequisites
Choosing a data structure is choosing which operations to make cheap. A phone book is fast to search by name and hopeless for finding everyone whose number ends in 7: the structure decides.
Index access
Index access means “give me the k-th element”. Arrays and ArrayList compute the address from the index in O(1). A linked list must walk k nodes. Hash maps and trees have no positions at all: they are addressed by key, and TreeMap cannot even report the k-th smallest key without walking.
Run the animation to see the difference between reading element 5 of an array and of a linked list.
| Structure | Get by position | Get by key |
|---|---|---|
| Array / ArrayList | O(1) | - |
| Sorted array | O(1) | O(log n) binary search |
| LinkedList | O(n) | - |
| ArrayDeque | Not supported | - |
| HashMap / HashSet | - | O(1) expected |
| TreeMap / TreeSet | Not supported (O(n) by iterating) | O(log n) |
| PriorityQueue | Only the root: peek() is O(1) | - |
Reading element k: array vs linked list
Interactive · edit the inputarray memory (value, address)
The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000.
1static int getFromArray(int[] arr, int k) {2return arr[k]; // address = base + k * 4: one memory read3}45static int getFromList(Node head, int k) {6Node cur = head;7for (int i = 0; i < k; i++) {8cur = cur.next; // pointer chase: one read per hop9}10return cur.value;11}
Live state
k
5
base
1000
Execution trace (dry run table)Show 3 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | k=5 base=1000 | The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000. |
| 2 | L2 | k=5 address=1020 | Address = 1000 + 5 × 4 = 1020. One multiplication and one addition give the location, no matter how large k is. |
| 3 | L2 | return=8 | return 8 with a single memory read: O(1) random access. |
Insert
Inserting at the end of an array list is amortised O(1); inserting anywhere else shifts the tail, O(n). A linked list inserts in O(1) once it holds the node, but *finding* the position costs O(n). Hash tables insert in expected O(1), balanced trees and heaps in O(log n).
A sorted array is the worst for insertion: O(log n) to find the spot, then O(n) to shift. That is the price of its O(1) minimum and O(log n) search.
| Structure | Insert |
|---|---|
| ArrayList | End: O(1) amortised; middle: O(n) |
| Sorted array | O(n): binary search O(log n) plus shifting |
| LinkedList | Ends: O(1); at index: O(n) to walk, O(1) to link |
| ArrayDeque | Ends: O(1) amortised; middle: not supported |
| HashMap / HashSet | O(1) expected |
| TreeMap / TreeSet | O(log n) |
| PriorityQueue | offer: O(log n) |
Delete
Deletion mirrors insertion. Removing the last element of an array list is O(1); removing from the front or the middle shifts the tail. Linked lists unlink in O(1) at either end of a doubly linked list. PriorityQueue.poll() removes the best element in O(log n), but remove(Object) must first search the heap, O(n).
| Structure | Delete |
|---|---|
| ArrayList | End: O(1); front or middle: O(n) |
| Sorted array | O(n) (shifting) |
| LinkedList | Ends: O(1); by value or index: O(n) to find |
| ArrayDeque | Ends: O(1); removeFirstOccurrence: O(n) |
| HashMap / HashSet | O(1) expected |
| TreeMap / TreeSet | O(log n) |
| PriorityQueue | poll: O(log n); remove(Object): O(n) |
Search
Search asks whether a value or key is present. Unsorted structures must scan, O(n). A sorted array uses binary search, O(log n). Hash tables answer in expected O(1) but only for *exact* keys, and containsValue on a map is O(n). Trees answer in O(log n) and also support ordered queries.
| Structure | Search (contains) |
|---|---|
| Array / ArrayList / LinkedList / ArrayDeque | O(n) |
| Sorted array | O(log n) |
| HashMap / HashSet | O(1) expected (containsKey); containsValue is O(n) |
| TreeMap / TreeSet | O(log n) |
| PriorityQueue | O(n) |
Min/max
The minimum and maximum are free in a sorted array (the two ends), cheap in a balanced tree (the leftmost and rightmost nodes, O(log n)) and, for one of them, free in a heap (the root). A hash table keeps no order, so it scans.
A heap gives only one extreme: a min-queue has the minimum at the root and finding the maximum means scanning. If you need both, use a TreeMap/TreeSet, or two heaps.
| Structure | Minimum | Maximum |
|---|---|---|
| Unsorted array / list / deque | O(n) | O(n) |
| Sorted array | O(1) | O(1) |
| HashMap / HashSet | O(n) | O(n) |
| TreeMap / TreeSet | O(log n) | O(log n) |
| PriorityQueue (min-heap) | O(1) | O(n) |
Predecessor/successor
The predecessor of x is the largest value below x; the successor is the smallest above it. They answer questions such as “which meeting starts right after 14:00?”. A sorted array finds them with binary search in O(log n); TreeSet.lower/higher/floor/ceiling do it in O(log n) in a tree. Hash tables and heaps cannot answer these without scanning everything.
| Structure | Predecessor / successor |
|---|---|
| Unsorted array / list / deque | O(n) |
| Sorted array | O(log n) |
| HashMap / HashSet | O(n) |
| TreeMap / TreeSet | O(log n): lowerKey, higherKey, floorKey, ceilingKey |
| PriorityQueue | Not supported (O(n) by scanning) |
1static void neighbours() {2TreeSet<Integer> starts = new TreeSet<>(List.of(900, 1030, 1400, 1530));3System.out.println(starts.lower(1400) + " " + starts.higher(1400) + " " + starts.ceiling(1401)); // 1030 1530 15304}
Range query
A range query returns everything with a key between lo and hi. Sorted structures locate the start in O(log n) and then walk the k results: O(log n + k). Unsorted structures must test every element, O(n), even when few match.
| Structure | All keys in [lo, hi] |
|---|---|
| Unsorted array / list / deque | O(n) |
| Sorted array | O(log n + k) |
| HashMap / HashSet | O(n) |
| TreeMap / TreeSet | O(log n + k) with subMap / subSet |
| PriorityQueue | O(n) |
1static List<Integer> inRange(TreeSet<Integer> set, int lo, int hi) {2return new ArrayList<>(set.subSet(lo, true, hi, true)); // finds lo in O(log n), then walks the results3}
Practice ladder
Now try it yourself
Easy
- Which structure gives O(1) minimum and O(log n) search?
- Why can ArrayDeque not return the k-th element?
Medium
- Design a data structure with insert, delete and getRandom in O(1)
- My Calendar I (use TreeMap)
Advanced
- Design a structure that supports insert, delete and k-th smallest in O(log n)
Key takeaways
- Every structure makes some operations cheap and others impossible or O(n).
- Order-based questions (min, max, predecessor, range) need a sorted structure; exact lookups favour hashing.
- Write down the dominant operations of a problem first, then read the matching column.