Arrays — The Complete Visual Reference

Memory models, Kotlin types, core operations, sorting algorithms, 14 algorithm patterns — visual diagrams, complexity tags & key insights
How Arrays Work in Memory
Contiguous block — O(1) random access via arithmetic: address(i) = base + (i × element_size) Index: [0] [1] [2] [3] [4] Addr: 0x1000 0x1004 0x1008 0x100C 0x1010 Value: 10 20 30 40 50 └─ 4 bytes per Int ─┘
Kotlin Array Types
Primitive arrays store raw values inline (no boxing). Reference arrays store heap-allocated object pointers. For 1M ints: IntArray = 4MB, Array<Int> = ~16MB.
TypeJVMSizeUse
IntArrayint[]4BInteger math — no boxing
LongArraylong[]8BLarge integers
DoubleArraydouble[]8BFloating point
BooleanArrayboolean[]1BFlags, visited arrays
CharArraychar[]2BCharacter manipulation
Array<T>T[]8B refObjects, nullables
PREFER IntArray over Array<Int> — no boxing, cache-friendly, 4x less memory
Creating & Accessing Arrays
// ── Creation ── val zeros = IntArray(5) // [0,0,0,0,0] val evens = IntArray(5) { it * 2 } // [0,2,4,6,8] val primes = intArrayOf(2,3,5,7,11) val words = Array(4) { "w$it" } // [w0,w1,w2,w3] val grid = Array(3) { IntArray(4) } // 3x4 matrix val nulls = arrayOfNulls<String>(3) // ── Access — all O(1) ── arr[0] arr.first() arr.last() arr.getOrNull(10) // null if OOB arr.getOrElse(10) { -1 } // fallback // ── Mutation — O(1) ── arr[0] = 99 arr.fill(0) arr.fill(7, 1, 4) // ── Properties ── arr.size arr.indices arr.lastIndex // ── Destructuring ── val (a, b, c) = arrayOf(1, 2, 3) // ── Equality & Printing ── arr1.contentEquals(arr2) // compare VALUES arr.contentToString() // "[1, 2, 3]" m.contentDeepToString() // 2D arrays // ── Swap (extension) ── fun IntArray.swap(i: Int, j: Int) { val t = this[i]; this[i] = this[j]; this[j] = t }
Iterating Arrays
// Index loop for (i in arr.indices) { arr[i] } // Value loop for (v in arr) { v } // Index + value for ((i,v) in arr.withIndex()) { } arr.forEachIndexed { i, v -> } // Reverse for (i in arr.indices.reversed()) { } // Step for (i in arr.indices step 2) { } // While (manual pointer) var i = 0 while (i < arr.size) { i++ }
Sorting & Searching
// ── Sorting — O(n log n) TimSort ── arr.sort() // in-place arr.sortedArray() // new copy arr.sortDescending() // in-place desc words.sortWith(compareBy { it.length }) students.sortWith( compareByDescending<Student> { it.grade } .thenBy { it.age }) // ── Searching ── arr.indexOf(5) // O(n) linear 5 in arr // O(n) contains arr.indexOfFirst { it > 3 } sorted.binarySearch(6) // O(log n) // ── Aggregates ── arr.min() arr.max() arr.sum() arr.average() arr.count { it > 0 }
Copying, Slicing & Reshaping
// ── Copying ── arr.copyOf() // full copy arr.copyOf(3) // truncate arr.copyOfRange(1, 4) // [1,4) // ── Slicing → returns List ── arr.slice(1..3) arr.take(3) arr.drop(2) arr.takeLast(2) arr.dropLast(1) arr.takeWhile { it < 5 } // ── Reverse ── arr.reversed() // List copy arr.reversedArray() // IntArray copy // ── Concatenate ── val combined = a + b // [1,2,3,4,5,6] // ── Conversions ── list.toIntArray() arr.toList() arr.toMutableList() list.toTypedArray()
λ
Functional Operations
All return new collections — original is never mutated.
// Transform arr.map { it * 2 } arr.mapIndexed { i, v -> "$i=$v" } arr.flatMap { listOf(it, it * 10) } // Filter arr.filter { it % 2 == 0 } arr.filterNot { it < 0 } arr.filterIndexed { i, _ -> i % 2 == 0 } // Aggregate arr.fold(0) { acc, v -> acc + v } arr.reduce { acc, v -> acc * v } arr.sumOf { it.toLong() } // Predicate checks arr.any { it > 9 } arr.all { it > 0 } arr.none { it < 0 } // Split & Group val (evens, odds) = arr.partition { it%2==0 } arr.groupBy { if (it%2==0) "E" else "O" } // Build & Combine arr.associate { it to it * it } // Map arr.zip(other) { a, b -> a + b } arr.distinct() // no dupes
Dynamic Arrays — MutableList
Internal buffer resizes at capacity: [1 2 3 4 _ _ _ _] size=4, cap=8 ↑ next add goes here O(1) When full → allocate 2x, copy all → O(n) Amortized per append: O(1)
val list = mutableListOf(10,20,30) list.add(40) // O(1) append list.addAll(listOf(50,60)) list.add(0, 5) // O(n) insert at 0 list.removeAt(0) // O(n) shift list.removeLast() // O(1) pop list.removeAll { it > 50 } // bulk remove list.retainAll { it % 10 == 0 } // Safe variants (null if empty) list.removeLastOrNull() list.removeFirstOrNull() // View (backed by original list) list.subList(1, 3) // [from, to) // Kotlin DSL builder val built = buildList { add(1); add(2) } // As Stack list.add(x) // push list.last() // peek list.removeLast() // pop O(1) // As Queue → use ArrayDeque val q = ArrayDeque<Int>() q.addLast(x) // enqueue q.removeFirst() // dequeue O(1)
2D Arrays & Matrix
// Create 3x4 matrix val m = Array(3) { r -> IntArray(4) { c -> r * 10 + c }} // Access: m[row][col] Dims: m.size × m[0].size // Transpose (square, in-place) for (r in 0 until n) for (c in r+1 until n) swap m[r][c] ↔ m[c][r] // Rotate 90° CW: transpose → reverse rows // Rotate 90° CCW: reverse rows → transpose // Ragged (jagged) array — variable row lengths val ragged = Array(4) { i -> IntArray(i+1) }
Bubble Sort
O(n²) O(1)
O(n²) avg/worst / O(n) best / O(1) / stable Repeatedly swap adjacent elements if out of order. Largest element "bubbles up" to the end each pass. [5 1 4 2 8] → swap 5,1 [1 5 4 2 8] → swap 5,4 [1 4 5 2 8] → swap 5,2 [1 4 2 5 8] → 5,8 ok. 8 is sorted. Repeat for remaining n-1 elements...
▶ Visualize
Selection Sort
O(n²) O(1)
O(n²) always / O(1) / unstable Find the minimum in the unsorted region, swap it to the front. [64 25 12 22 11] min=11 [11|25 12 22 64] min=12 [11 12|25 22 64] min=22 [11 12 22|25 64] min=25 [11 12 22 25 64] done ↑sorted|unsorted↑
▶ Visualize
Insertion Sort
O(n²) O(1)
O(n²) worst / O(n) best / O(1) / stable Take the next element, insert into its correct position in the sorted prefix by shifting larger elements right. [5|1 4 2 8] insert 1 → shift 5 [1 5|4 2 8] insert 4 → shift 5 [1 4 5|2 8] insert 2 → shift 5,4 [1 2 4 5|8] insert 8 → already ok [1 2 4 5 8] done
▶ Visualize
Merge Sort
O(n log n) / O(n) / stable Divide in half, sort each, merge: [38 27 43 3 9 82 10] [38 27 43 3] [9 82 10] [38 27] [43 3] [9 82] [10] [27 38] [3 43] [9 82] [10] merge pairs [3 27 38 43] [9 10 82] merge halves [3 9 10 27 38 43 82] final merge log n levels × O(n) merge per level = O(n log n)
▶ Visualize
Quick Sort
O(n log n) avg / O(n²) worst / O(log n) / unstable Pick pivot, partition, recurse: [3 6 8 10 1 2 1] pivot=6 [3 1 2 1] 6 [8 10] partition around 6 ↓ recurse ↓ recurse [1 1 2 3] 6 [8 10] [1 1 2 3 6 8 10] Partition: two-pointer scan, swap elements to correct side of pivot. O(n) per level. Avg log n levels → O(n log n) Worst (already sorted + bad pivot) → O(n²)
▶ Visualize
Heap Sort
O(n log n) O(1)
O(n log n) guaranteed / O(1) / unstable Build a max-heap, then repeatedly extract the maximum. Phase 1: Build max-heap in O(n) heapify from last internal node upward [4 10 3 5 1] → [10 5 3 4 1] (max-heap) Phase 2: Extract max n times swap root ↔ last, shrink heap, sift down [10 5 3 4 1] → swap → [1 5 3 4|10] sift down 1 → [5 4 3 1|10] → extract 5...
▶ Visualize
Counting Sort
O(n+k) O(k)
O(n+k) / O(k) / stablek = range of values Count frequency of each value, then place directly: input: [4 2 2 8 3 3 1] Step 1: count frequencies idx: 1 2 3 4 ... 8 cnt: 1 2 2 1 1 Step 2: prefix sums (cumulative counts) Step 3: place elements right-to-left (stable) output: [1 2 2 3 3 4 8]
▶ Visualize
Radix Sort
O(d(n+k)) O(n+k)
O(d·(n+k)) / O(n+k) / stabled=digits, k=base Sort by each digit position (LSD → MSD) using a stable sort: [170 45 75 90 802 24 2 66] ones: [170 90 802 2 24 45 75 66] tens: [802 02 24 45 66 170 75 90] hunds: [2 24 45 66 75 90 170 802]
▶ Visualize
Bucket Sort
O(n+k) avg O(n)
O(n+k) avg / O(n²) worst / O(n) / stable Distribute elements into buckets, sort each bucket, concatenate: [0.78 0.17 0.39 0.26 0.72 0.94] B0:[0.17] B2:[0.26 0.39] B7:[0.72 0.78] B9:[0.94] Sort each bucket (insertion sort) Concatenate: [0.17 0.26 0.39 0.72 0.78 0.94]
When to Use
uniform float distributionWhy: When input values are uniformly distributed in a known range (e.g., [0, 1)), elements spread evenly across buckets, giving O(n) average time — the ideal case for bucket sort. scatter-gather sortingWhy: The distribute-sort-merge pattern (scatter into buckets, sort each, gather results) naturally parallelizes and works well when data can be partitioned into independent ranges.
▶ Visualize
Sorting Algorithm Comparison
Algorithm Best Average Worst Space Stable Notes
Bubble SortO(n)O(n²)O(n²)O(1)YesEarly exit if no swaps; educational only
Selection SortO(n²)O(n²)O(n²)O(1)NoMinimum swaps (n-1); always O(n²)
Insertion SortO(n)O(n²)O(n²)O(1)YesBest for nearly sorted & small arrays; online
Merge SortO(n log n)O(n log n)O(n log n)O(n)YesGuaranteed; great for linked lists
Quick SortO(n log n)O(n log n)O(n²)O(log n)NoFastest in practice; pivot choice critical
Heap SortO(n log n)O(n log n)O(n log n)O(1)NoIn-place guaranteed; poor cache locality
Counting SortO(n+k)O(n+k)O(n+k)O(k)Yesk = value range; integers only
Radix SortO(d(n+k))O(d(n+k))O(d(n+k))O(n+k)Yesd = digits; uses counting sort per digit
Bucket SortO(n+k)O(n+k)O(n²)O(n)YesUniform distribution required
Tim SortO(n)O(n log n)O(n log n)O(n)YesKotlin/JVM default for objects; hybrid merge+insertion
Two Pointers — Convergent
O(n) O(1)
arr: [1 3 5 7 9 11] target = 12 L→ ←R sum = 1+11 = 12 ✓ found sum < target → L++ sum > target → R--
When to Use
two sum (sorted)Why: Sorted order means L+R too small → L++, too big → R--. Eliminates brute-force O(n²) pair scanning into a single O(n) pass from both ends. valid palindromeWhy: Symmetric structure — compare char at L with char at R, move inward. If they always match, it's a palindrome. Natural fit for converging pointers. container with most waterWhy: Width decreases as pointers converge, so you must move the shorter wall inward — it's the only pointer that could yield a taller container. Greedy + convergent. 3SumWhy: Fix one element, then two-pointer the sorted remainder. Sorting enables skipping duplicates and the L/R convergence reduces the inner loop from O(n) brute-force to O(n) two-pointer. squares of sorted arrayWhy: Negative numbers have large squares on the left, positives on the right. Converge from both ends, picking the larger square each step into a result array. Single pass O(n).
▶ Visualize
Two Pointers — Same Direction
O(n) O(1)
Remove duplicates from sorted: [0 0 1 1 1 2 2 3] S F nums[F]≠nums[S] → S++, write nums[F] result: [0 1 2 3 ...] slow = write head fast = read head
When to Use
remove duplicatesWhy: Sorted input means dupes are adjacent. Slow pointer marks the write head; fast pointer scans. When fast finds a new value, write it at slow. No extra space needed. move zerosWhy: Slow pointer tracks where the next non-zero should go. Fast scans all elements. Non-zeros get written at slow; remaining slots are filled with zeros. In-place, order-preserving. partition arrayWhy: Need to rearrange elements by a condition (e.g., negatives before positives) without extra space. Slow marks the boundary, fast scans and swaps qualifying elements forward. remove elementWhy: "Remove element" or "keep only matching" — fast reads every element, slow only advances when an element passes the filter. Result length = slow pointer position. merge sorted array in-placeWhy: Two sorted arrays, one with trailing space. Start from the ends (largest elements) and fill backwards. Both pointers move in the same direction (right to left). O(n+m) time, O(1) extra space. string compressionWhy: Read pointer scans runs of identical chars; write pointer writes char + count. Classic slow/fast pattern where slow = compressed write position and fast = read position. remove duplicates IIWhy: Allow at most 2 duplicates in sorted array. Same slow/fast pointers but track count of current value. sort by parityWhy: Move even numbers to even indices, odd to odd indices. Two-pointer placement by parity.
▶ Visualize
Sliding Window — Fixed Size
O(n) O(1)
Max sum of subarray size k=3: [2 1 5 1 3 2] window slides → └──k──┘ slide: +incoming -outgoing sum = sum + arr[R] - arr[R-k] No recomputation — O(1) per slide
When to Use
max sum subarray of size KWhy: Fixed window size means each slide adds one element and removes one. Instead of recomputing the sum from scratch (O(k) per position), update incrementally in O(1). substring of length K (no repeats)Why: Any property of a fixed-length substring can be maintained incrementally — add the incoming char's effect, remove the outgoing char's effect. One pass, O(n) total. find all anagramsWhy: An anagram of pattern p has exactly length p. Slide a window of size |p| over s, maintaining a frequency map. When window freq matches p's freq, you found an anagram. max points from cardsWhy: Take k cards from either end = total sum minus a window of size n-k. Slide the n-k window to find the minimum interior sum. Answer = total - minWindow. Fixed-size window trick. grumpy bookstore ownerWhy: Customers during non-grumpy minutes are guaranteed. Slide a window of size k to find where suppressing grumpiness rescues the most extra customers. Fixed window maximizes bonus. duplicate within K distanceWhy: Check if any value appears twice within a window of size k. Sliding window + set. count subarrays avg >= thresholdWhy: Fixed window of size k, check if average meets threshold. Direct sliding window sum check.
▶ Visualize
Sliding Window — Variable Size
O(n) O(k)
Longest substring, no repeats: "a b c a b c b b" L─────R expand R L───R shrink L (dup 'a') pattern: expand R always shrink L while constraint violated update best = max(best, R-L+1)
When to Use
longest substring no repeatWhy: Window expands right greedily. When a duplicate enters, shrink left past the previous occurrence. A Set or lastSeen map tracks window contents. Each char enters/leaves once → O(n). minimum window substringWhy: Expand right to satisfy the constraint (contain all target chars). Once satisfied, shrink left to minimize length. Frequency maps track "have" vs "need" counts. at most K distinct charsWhy: Expand right freely. When distinct count exceeds K, shrink left until it drops back to K. A frequency map tracks counts; remove a key when its count hits zero. min size subarray sumWhy: Expand right to grow the sum. Once sum ≥ target, shrink left to find the minimum length that still satisfies. The "satisfy then minimize" pattern of variable windows. longest repeating char replacementWhy: Window tracks char frequencies. Valid if windowLen - maxFreq ≤ k (can replace the rest). Expand right always, shrink left when replacements exceed k. O(n) with frequency map. fruit into baskets (2 types)Why: Longest subarray with at most 2 distinct values. Variable window with a frequency map — shrink left when distinct count exceeds 2. Same as "at most K distinct" with K=2. longest subarray sum <= targetWhy: Shrink window when sum exceeds target. Variable-size window with sum constraint. max ones (K flips)Why: Longest subarray of 1s if you can flip at most k 0s. Track zero count in window.
▶ Visualize
Σ
Prefix Sums
O(n) build O(n)
nums: [1 3 5 7 9 11] prefix: [0 1 4 9 16 25 36] rangeSum(1,3) = prefix[4] - prefix[1] = 16 - 1 = 15 (3+5+7)O(1) per query
prefix[i+1] = prefix[i] + nums[i]   |   rangeSum(L,R) = prefix[R+1] - prefix[L]
When to Use
range sum queryWhy: Pre-build cumulative sums once O(n). Then any range [L,R] sum is prefix[R+1]-prefix[L] in O(1). Turns repeated O(n) queries into O(1) each. subarray sum equals KWhy: If prefixSum[j] - prefixSum[i] = k, then subarray [i,j) sums to k. Store prefix sum counts in a HashMap — check if (currentSum - k) was seen before. O(n) total. product except selfWhy: Left-prefix product gives product of all elements left of i. Right-suffix product gives all right of i. Multiply them for the answer. Avoids division, handles zeros. 2D matrix region sumWhy: Extension to 2D: build a prefix sum matrix using inclusion-exclusion. Any rectangle sum becomes O(1) with four lookups: add, subtract overlaps, add back double-subtracted corner. find pivot indexWhy: Find index where left sum equals right sum. Total prefix sum lets you check leftSum == totalSum - leftSum - nums[i] at each index in O(1). Single pass after prefix build. contiguous array (0s and 1s)Why: Replace 0→-1, then longest subarray with sum 0 = equal 0s and 1s. Use prefix sum + HashMap: if same prefix sum seen before, the subarray between is balanced.
▶ Visualize
Kadane's Algorithm
O(n) O(1)
Maximum subarray sum: [-2 1 -3 4 -1 2 1 -5 4] └─── max = 6 ───┘ at each i: current = max(nums[i], current + nums[i]) best = max(best, current) "extend or restart" — if running sum goes negative, start fresh at nums[i]
maxEndingHere(i) = max( nums[i], maxEndingHere(i-1) + nums[i] )
When to Use
maximum subarray sumWhy: At each position, decide: extend the current subarray or start fresh. If the running sum goes negative, it can only hurt future elements — restart. Greedy DP in O(n). max circular subarrayWhy: A wrapping subarray = total sum minus the minimum subarray in the middle. Run Kadane's for both max and min subarrays. Answer = max(standard, total - minSubarray). max subarray with indicesWhy: Same Kadane's logic, but track currStart (reset when restarting) and update bestStart/bestEnd when a new best is found. Indices come for free from the extend-or-restart decision. best time to buy/sell stockWhy: Track minimum price so far (buy point). At each price, profit = price - minSoFar. Same "extend or restart" idea — if price drops below minSoFar, reset the buy point. maximum product subarrayWhy: Track both max and min running product (a negative min can flip to max). At each element: newMax = max(num, maxProd*num, minProd*num). Kadane's variant with min/max tracking.
▶ Visualize
Binary Search
O(log n) O(1)
Find 7 in sorted array: [1 3 5 7 9 11 13] L M R mid = L + (R-L)/2 ← avoids overflow nums[M] == target → return M nums[M] < target → L = M+1 nums[M] > target → R = M-1
When to Use
find target in sorted arrayWhy: Sorted = monotonic. Compare middle element to target: if smaller, discard left half; if larger, discard right half. Halves the search space each step → O(log n). first & last positionWhy: Standard binary search stops at any match. To find the first: on match, record it but keep searching left (R=mid-1). For last: keep searching right (L=mid+1). Same O(log n). search in rotated arrayWhy: A rotated sorted array has two sorted halves. At each step, determine which half is sorted (compare L to mid). If target falls in the sorted range, search there; otherwise search the other half. search on answer (Koko eating bananas)Why: When the answer space is monotonic ("is X feasible?" flips from false→true), binary search over the answer itself. Test the midpoint with a feasibility check. Parametric search pattern. peak elementWhy: Not fully sorted, but locally monotonic. If mid < mid+1, a peak exists to the right; otherwise to the left. Binary search on slope direction finds any peak in O(log n). koko eating bananasWhy: Classic "search on answer" — binary search over eating speed k. For each k, check if Koko finishes in time (feasibility = O(n)). Monotonic: higher speed always finishes faster. O(n log max). capacity to ship within D daysWhy: Binary search on capacity [max(w)..sum(w)]. For each candidate, greedily pack weights into days. Monotonic: higher capacity always needs fewer days. O(n log sum). search insert positionWhy: Find index where target would be inserted in sorted array. Classic binary search returning lo when not found.
▶ Visualize
Dutch National Flag
O(n) O(1)
3-way partition (0s, 1s, 2s): before: [2 0 2 1 1 0 0 2 1] after: [0 0 0 1 1 1 2 2 2] low mid high invariants at each step: arr[0..low-1] = all 0s arr[low..mid-1] = all 1s arr[mid..high] = unexplored arr[high+1..n-1] = all 2s
When to Use
sort colors (0,1,2)Why: Only 3 distinct values — don't need a full sort. Three pointers carve out regions for 0s, 1s, and 2s in a single pass. O(n) time, O(1) space — faster than O(n log n) sort. three-way partitionWhy: Generalization: any time you need to split elements into exactly 3 groups (less than, equal to, greater than a pivot) in one pass. Used in quicksort optimization for duplicate-heavy data. partition around pivotWhy: Elements < pivot go left, = pivot stay middle, > pivot go right. The three-pointer invariant ensures each element is examined at most once. Foundation of quicksort's partition step. sort array by parityWhy: Two categories (even/odd) = simplified Dutch flag with just 2 groups. One pointer tracks the boundary, other scans — swap evens to the front. Single pass, O(1) space. negatives / zeros / positivesWhy: Three-way partition variant: group negatives, zeros, and positives. Same lo/mid/hi pointer technique.
▶ Visualize
#
Frequency Counting
O(n) O(k)
Two approaches: IntArray(26) ← lowercase chars only, O(1) space freq[ch - 'a']++ HashMap<T,Int> ← any type, O(k) space freq[x] = (freq[x] ?: 0) + 1 or groupingBy { it }.eachCount()
When to Use
anagram checkWhy: Anagrams have identical character frequencies. Count chars for string s (increment), then for string t (decrement). If all counts are zero, they're anagrams. O(n) time, O(1) space for fixed alphabet. first non-repeating elementWhy: First pass: build frequency map. Second pass: scan left-to-right, return first char with count=1. Two O(n) passes. The map lets you check uniqueness in O(1) per char. majority element (Boyer-Moore)Why: Boyer-Moore Voting: maintain a candidate and count. Same element → count++, different → count--. When count hits 0, switch candidate. The majority element survives all cancellations. O(1) space. character frequency countWhy: Count occurrences of each character/element. Foundation for anagram checks, frequency-based problems. group anagramsWhy: All anagrams share the same sorted-character key (or frequency signature). Use that as a HashMap key to bucket strings. O(n·k) where k = avg string length. top-K frequent elementsWhy: Build frequency map in O(n), then extract top K by sorting entries by count or using a min-heap of size K. The counting step is the core — heap/sort is just the extraction.
▶ Visualize
Monotonic Stack
O(n) O(n)
Next Greater Element: temps: [73 74 75 71 69 72 76 73] stack: [indices of unresolved elements] Process 72 (idx 5): stack has [75,71,69] → pop 69,71 (< 72) answer[4]=72, answer[3]=72, push 72 Process 76 (idx 6): stack has [75,72] → all < 76 pop 72 → answer[5]=76 pop 75 → answer[2]=76 push 76 Maintain: decreasing stack for "next greater" increasing stack for "next smaller" Each element pushed/popped at most once → O(n)
When to Use
next greater elementWhy: For each element, you need the first larger element to its right. A decreasing stack keeps candidates. When a new element is larger than the top, it's the answer for all smaller tops — pop and record. O(n) total since each index is pushed/popped once. daily temperaturesWhy: "How many days until warmer?" = next greater element by index difference. Stack stores indices of unresolved days. When a warmer day arrives, pop all cooler days and compute the gap. trapping rain waterWhy: Water is trapped between taller bars. A decreasing stack tracks bars. When a taller bar arrives, pop shorter bars — water between the popped bar and the new taller bar fills the gap. Handles layer-by-layer. largest rectangle in histogramWhy: For each bar, you need the nearest shorter bar on both sides. An increasing stack tracks bars. When a shorter bar arrives, popped bars can't extend further — compute their max rectangle. O(n) total. stock span problemWhy: "How many consecutive days was price ≤ today?" = distance to previous greater element. Decreasing stack of indices; when today's price pops smaller prices, span = today - stack.top. O(n) amortized. remove K digitsWhy: Greedily remove digits that break non-decreasing order (left to right). Monotonic increasing stack: if incoming digit < top and removals left, pop the top. Ensures the smallest remaining number. next smaller elementWhy: Find next smaller element for each position. Monotonic increasing stack — mirror of next greater element.
▶ Visualize
Quick Select (Kth Element)
O(n) avg O(1)
Find 2nd smallest (k=1, 0-indexed): [3 2 1 5 6 4] pivot=5 partition → [3 2 1 4 5 6] pivotIdx=4 k=1 < 4 → recurse LEFT [0..3] [3 2 1 4] pivot=1 [1 2 3 4] pivotIdx=0 k=1 > 0 → recurse RIGHT [1..3] [_ 2 3 4] pivot=3, pivotIdx=2 k=1 < 2 → recurse LEFT [1..1] single element → answer = arr[1] = 2
When to Use
kth smallest elementWhy: Full sort is O(n log n) but you only need one element. Quick Select partitions like quicksort but recurses into only ONE side — the side containing k. Average O(n), only processes elements that could be the answer. find medianWhy: Median = element at index n/2 in sorted order. Quick Select finds this in O(n) average without sorting the entire array. Much faster than O(n log n) full sort for a single order statistic. top-K elementsWhy: After Quick Select places the kth element correctly, all elements left of k are smaller (unsorted). You get the top-K partition in O(n) average — then sort just those K elements if order matters. kth largest in streamWhy: Maintain a min-heap of size k. Each new element: if larger than heap root, replace root and heapify. Root is always the kth largest. O(n log k) total, O(k) space. Quick Select variant for static arrays.
▶ Visualize
Cyclic Sort
O(n) O(1)
Place each number at its correct index: Array contains values 1..n (or 0..n-1) [3 1 5 4 2] nums[0]=3 → should be at idx 2 swap nums[0] ↔ nums[2] [5 1 3 4 2] nums[0]=5 → should be at idx 4 swap nums[0] ↔ nums[4] [2 1 3 4 5] nums[0]=2 → should be at idx 1 swap nums[0] ↔ nums[1] [1 2 3 4 5] all in place! Rule: while nums[i] ≠ i+1 → swap to correct spot After sorting: missing/duplicate = index mismatch
When to Use
find missing numberWhy: Array has values in range [0,n] or [1,n] — each value has a "home" index. Place each number at its home, then scan for the index where nums[i] ≠ expected. That index reveals the missing number. O(n) time, O(1) space. find all duplicatesWhy: Place each number at index (val-1). If nums[target] already equals the value, it's a duplicate. After one pass of cyclic placement, scan for mismatches — each mismatch is a duplicate. No extra space needed. first missing positiveWhy: Ignore negatives and values > n. Place valid positives at index (val-1). After placement, the first index where nums[i] ≠ i+1 gives the first missing positive. O(n) time, O(1) space — the optimal solution. find corrupt pairWhy: One number duplicated, one missing (corrupt sequence). Cyclic sort places numbers at home indices. The index with a wrong number reveals both: the wrong number is the duplicate, and (index+1) is the missing one. sort 1-to-N in placeWhy: Place each value v at index v-1. The foundational cyclic sort operation that enables missing/duplicate detection.
▶ Visualize
In-Place Rotation & Reversal
O(n) O(1)
Rotate right by k=3 (triple reversal): [1 2 3 4 5 6 7] original [7 6 5 4 3 2 1] 1. reverse all [5 6 7 4 3 2 1] 2. reverse [0..k-1] [5 6 7 1 2 3 4] 3. reverse [k..n-1]
When to Use
rotate right by KWhy: Triple reversal trick: reverse all, reverse first k, reverse rest. Each reversal is O(n) in-place. Avoids the naive O(n·k) approach of shifting one position at a time, and uses O(1) extra space. reverse a portionWhy: Reverse a subarray between two indices. Building block for rotation and word reversal algorithms. reverse words in stringWhy: Reverse the entire string, then reverse each word individually. The double reversal cancels out within each word but flips word order. Classic in-place string manipulation using the same reversal primitive. matrix rotate 90°Why: Transpose (swap rows↔cols) then reverse each row = 90° clockwise. Both operations are in-place O(n²). Decomposing rotation into two simple transforms avoids complex index mapping. next permutationWhy: Find rightmost ascending pair (i, i+1), swap nums[i] with the smallest larger element to its right, then reverse the suffix. Uses the reversal primitive for the final step. O(n) in-place. spiral matrixWhy: Traverse matrix in spiral order by maintaining top/bottom/left/right boundaries. After each row/column traversal, shrink the corresponding boundary. Reversal/rotation thinking helps with direction changes.
  1. Handle k = k % n (k can be > array length)
  2. Reverse entire array
  3. Reverse first k elements
  4. Reverse remaining n-k elements
  5. Rotate left by k = rotate right by n - k
▶ Visualize
Merge Intervals
O(n log n) O(n)
Sort by start, then merge overlapping: input: [1,3] [2,6] [8,10] [15,18] 1━━━3 2━━━━━6 overlap → extend 8━━10 15━━18 output: [1,6] [8,10] [15,18] merge if: current.start <= last.end extend: last.end = max(last.end, current.end)
When to Use
merge overlapping intervalsWhy: Sorting by start time groups overlapping intervals adjacently. Then a single greedy pass merges: if current.start ≤ last.end, they overlap — extend. Otherwise, start a new group. O(n log n) for sort. insert intervalWhy: Already sorted list: add all intervals before the new one, merge all that overlap with it, then add all after. Three phases, single pass O(n). Sorting is already done. meeting roomsWhy: Sort start times and end times separately. Sweep through starts: if a meeting starts before the earliest ending, you need another room. Otherwise, reuse a freed room. Counts peak overlap. employee free timeWhy: Merge all busy intervals, then the gaps between merged intervals are the free slots. Same merge pattern, just extract the complement. Sort + merge + gap extraction. non-overlapping intervalsWhy: Minimum removals to eliminate all overlaps. Sort by end time, greedily keep intervals that don't overlap with the last kept. Count of removed = total - kept. O(n log n). minimum meeting roomsWhy: Sort arrival and departure times separately. Sweep through arrivals: if an arrival comes before the earliest departure, need another platform. Same as meeting rooms — count peak concurrency.
▶ Visualize
Off-By-One & Overflow
// WRONG: last index is size-1, not size arr[arr.size] // ArrayIndexOutOfBoundsException // RIGHT: arr[arr.lastIndex] // or arr[arr.size - 1] // WRONG: binary search overflow val mid = (lo + hi) / 2 // can overflow! // RIGHT: val mid = lo + (hi - lo) / 2 // WRONG: sum overflows Int arr.sum() // RIGHT: use Long accumulator arr.fold(0L) { acc, v -> acc + v } // WRONG: == compares REFERENCES on arrays! intArrayOf(1,2) == intArrayOf(1,2) // false! // RIGHT: use contentEquals intArrayOf(1,2).contentEquals(intArrayOf(1,2))
Algorithm Complexity at a Glance
Algorithm Time Space Key Requirement
Two Pointers (convergent)O(n)O(1)Sorted array or symmetric structure
Two Pointers (same-dir)O(n)O(1)In-place read/write partitioning
Sliding Window (fixed)O(n)O(1)Subarray of fixed length k
Sliding Window (variable)O(n)O(k)Constraint-based window expansion
Prefix Sum (build)O(n)O(n)Range queries needed after build
Prefix Sum (query)O(1)After O(n) preprocessing
Kadane's AlgorithmO(n)O(1)Contiguous subarray optimization
Binary SearchO(log n)O(1)Monotonic / sorted search space
Dutch National FlagO(n)O(1)3-way partitioning (3 distinct groups)
Frequency CountingO(n)O(k)Count distinct elements (k = distinct)
Array RotationO(n)O(1)Triple reversal trick
Merge IntervalsO(n log n)O(n)Sort dominates; greedy merge
Monotonic StackO(n)O(n)Next greater/smaller element queries
Quick SelectO(n) avgO(1)Kth element without full sort
Cyclic SortO(n)O(1)Values in range [1,n] or [0,n-1]
Which Pattern?
If You See...Try This
"sorted array" + pair/tripletTwo Pointers (converge)
"in-place" + remove/filterTwo Pointers (same-dir)
"subarray of size k"Sliding Window (fixed)
"longest/shortest with constraint"Sliding Window (variable)
"range sum" or "subarray sum = k"Prefix Sums + HashMap
"max contiguous subarray"Kadane's
"sorted" + "find" or "min X s.t."Binary Search
"partition into 3 groups"Dutch National Flag
"anagram" / "frequency" / "unique"Frequency Counting
"rotate" / "reverse words"Triple Reversal
"overlapping intervals"Sort + Merge Intervals
"next greater" / "next warmer"Monotonic Stack
"kth largest" / "median" / "top K"Quick Select
"missing number" in range [1,n]Cyclic Sort
"histogram" / "trapping rain water"Monotonic Stack
Core Array Operations — Complexity
Operation IntArray MutableList ArrayDeque
Access by indexO(1)O(1)O(1)
Update by indexO(1)O(1)O(1)
Append to endN/A (fixed)O(1) amortizedO(1) amortized
Prepend to frontN/A (fixed)O(n) shiftO(1) amortized
Insert at indexN/A (fixed)O(n) shiftO(n) shift
Delete at indexN/A (fixed)O(n) shiftO(n) shift
Linear searchO(n)O(n)O(n)
Binary search (sorted)O(log n)O(log n)
SortO(n log n)O(n log n)