Chapter 13: Patterns & Miscellaneous
Master essential algorithmic patterns that appear across countless interview problems: interval manipulation, monotonic stacks, sliding window optimizations, divide and conquer strategies, greedy reasoning, and line sweep techniques. These patterns are the connective tissue between data structures and problem-solving mastery.
- Topics
- 7
- Demos
- 4
- Videos
- 3
- Min read
- 36
Chapter Overview
This final chapter covers algorithmic patterns that do not fit neatly into a single data structure category but appear with remarkable frequency in interviews and real-world systems. Each pattern represents a reusable problem-solving template.
Interval problems teach you to reason about overlapping ranges---scheduling meetings, merging time slots, or detecting conflicts. Monotonic stacks unlock efficient solutions to "next greater element" problems that would otherwise require nested loops. Sliding window maximum combines deques with the sliding window pattern for O(n) range-max queries.
Divide and conquer shows how breaking a problem into independent subproblems (and combining their solutions) leads to elegant algorithms like merge sort and Karatsuba multiplication. Greedy algorithms demonstrate when making the locally optimal choice at each step provably yields the globally optimal result. Line sweep transforms 2D geometric or scheduling problems into sorted event processing.
Mastering these patterns is about recognizing which template applies to a new problem. Once you identify the pattern, the implementation follows naturally from the template.
Chapter Roadmap
Click any topic to jump in
Interval Problems
Representation, overlap detection, and linear-time insertion into sorted intervals.
Merge Intervals
$O(n \log n)$ sort-and-sweep with careful edge-case handling.
Each tackles a different aspect
Monotonic Stack
Amortized $O(n)$ next/previous greater/smaller via stack invariants.
Sliding Window Maximum
Monotonic deque gives $O(n)$ — beating heap-based $O(n \log k)$.
Each tackles a different aspect
Divide and Conquer
Master Theorem template and Kadane as the linear alternative to $O(n \log n)$.
Greedy Algorithms
Greedy-choice property, exchange arguments, and the Jump Game reach invariant.
Line Sweep
Event-based processing for maximum-overlap and meeting-rooms problems.
Topics
7 topics in this chapter
A calendar app receives a new meeting request and must say at once whether it clashes with anything already booked. A video editor must know which clips cover the playhead, and a memory allocator must find the reserved block that contains an address. Comparing the new range with every stored range works, but the comparison itself is fiddly: two ranges can be disjoint, touch at an endpoint, partly overlap, or one can contain the other, and hand-written case lists usually miss one.
The previous chapter ended with Segment Tree, which answered aggregate queries over fixed index ranges. This final chapter turns to reusable patterns, and intervals come first because ranges appear everywhere. An interval is a pair [start, end], and nearly all interval logic reduces to one test: two intervals overlap exactly when each starts no later than the other ends. Sorting by start then lines the ranges up so that related intervals sit next to each other, and a single left-to-right pass replaces the all-pairs comparison.
We first derive the overlap test and its endpoint conventions. Then we insert a new interval into a sorted, non-overlapping list in one linear pass.
Definition
An interval with is the set of points from a to b. Two closed intervals and overlap if and only if and , and their intersection is then . For half-open intervals the inequalities become strict. Interval problems ask how a collection of such ranges relate: which overlap, how to combine them, and where a new one fits.
In this topic
- 1Interval Representation and Overlap Detection
- 2Insert Interval
Interval Representation and Overlap Detection
Intervals A = [a1, b1] and B = [a2, b2] relate in six ways: either one entirely first, a partial overlap from either side, or containment. Rather than test each case, negate the easy ones. They are disjoint exactly when b1 < a2 or b2 < a1. By De Morgan's law they overlap when a1 <= b2 and a2 <= b1, one test covering all four overlapping cases. The shared part is [max(a1, a2), min(b1, b2)]. Whether touching endpoints count depends on the convention: closed [1, 5] and [5, 8] share the point 5, while half-open meeting slots do not.
Two intervals and overlap iff (the standard two-inequality test). Detecting overlap is ; detecting pairwise overlaps in a set is naively but after sorting by start (sweep-line). The convention on closed vs. half-open endpoints changes which inequalities are strict — be consistent.
Do intervals [1, 5] and [3, 8] overlap? What about [1, 5] and [6, 9]?
Insert Interval
Given intervals sorted by start that do not overlap, insert a new interval [l, r] and merge whatever it touches. Because the list is sorted, the existing intervals fall into three consecutive groups. Those with end < l finish before the new one begins, so they are copied unchanged. Those that overlap [l, r] are absorbed: l becomes the smallest start and r the largest end among them. The rest, with start > r, are copied after the merged interval. One pass costs . Binary search finds the boundaries in , but building the output still costs .
Given a sorted list of non-overlapping intervals and a new interval , insert in : copy intervals ending before , merge all intervals overlapping by taking of starts and of ends, then copy remaining intervals. Linear time is optimal since the output may differ in up to positions.
Insert [4, 8] into [[1, 2], [3, 5], [6, 7], [8, 10], [12, 16]].
Related Problems on PixelBank
A monitoring system logs outages as time ranges, and the monthly report needs total downtime. The raw log overlaps heavily: two alerts fire for the same incident, one short outage sits inside a longer one, and simply summing the lengths counts the same minutes twice. The ranges must first be collapsed into disjoint blocks.
The previous topic, Interval Problems, gave the overlap test and inserted one interval into a list that was already sorted and clean. Merge Intervals handles the general case: an unsorted pile with arbitrary overlaps. Comparing every pair costs , and overlaps also chain, since A may overlap B and B overlap C while A and C are disjoint, so one round of pairwise merging is not enough. Sorting by start fixes both problems. After sorting, any interval that overlaps the block being built comes next in order, and once an interval starts beyond the block's end, no later interval can reach back into it. A single scan therefore closes each block exactly once.
We first build the sort-and-merge scan and argue why it is correct. Then we work through the edge cases that break careless versions: touching endpoints, containment, and empty input.
Definition
Merging intervals takes a collection of intervals and returns the smallest set of pairwise disjoint intervals whose union equals the union of the input. Sorting by start, then extending the last output interval whenever the next start is at most its end, solves it in time, dominated by the sort, plus space for the output. The scan itself is linear.
In this topic
- 1Sort-and-Merge Strategy
- 2Edge Cases in Merging
Sort-and-Merge Strategy
Sort the intervals by start, then keep a merged list whose last entry is the block being grown. For each interval, compare its start with that block's end. If start <= end, the interval overlaps or touches the block, so extend the block's end to the larger of the two ends. Otherwise the interval begins after the block ends, so the block is final and the interval opens a new one. Correctness rests on the sort: every later interval starts at least as late as the current one, so if the current one cannot reach the block, nothing after it can. Sorting costs and the scan .
Sort intervals by start time (), then sweep: for each interval, if it overlaps the last merged interval (), extend the end; otherwise start a new group. Total time , dominated by sorting. Correctness: after sorting, any interval overlapping a later one must also overlap any merged block between them.
Merge intervals [[1,3], [2,6], [8,10], [15,18]].
Edge Cases in Merging
Four inputs break careless merges. Touching intervals such as [1, 2] and [2, 3] share the point 2, so with closed intervals the test must be start <= end, not start < end. A long interval that contains later ones, such as [1, 10] followed by [2, 3], needs end = max(end, current end); plain assignment would shrink the block to [1, 3]. Empty input must return an empty list rather than crash. And extending the caller's interval lists in place changes their data, so copy each interval first. Each fix is one line of the same scan.
Key edge cases: (1) touching intervals like and — use not in the overlap check to merge them; (2) fully contained intervals and — second is absorbed without extending end; (3) empty input; (4) single interval. All collapse into the same sweep-line invariant: the merged set remains sorted and non-overlapping after each step.
Merge [[1,10], [2,3], [4,5], [6,7]].
Related Problems on PixelBank
A stock tracker wants, for each day, the next day on which the price is higher. The direct method scans forward from every day until it finds a larger price, and on a falling market each scan runs to the end of the array, for comparisons in total. The waste is that every scan re-reads prices that earlier scans have already examined.
The previous topic, Merge Intervals, turned a quadratic pairwise problem into one pass by sorting. The monotonic stack reaches a single pass without sorting, by keeping only the elements that could still matter. Scan left to right and hold on a stack the elements still waiting for their answer. Because an element is popped as soon as something larger appears, the waiting elements are always in decreasing order from bottom to top; that ordering is the invariant. When a new element arrives, it resolves every smaller waiting element on top at once. Each element is pushed once and popped at most once, so the whole scan is linear.
We first solve Next Greater Element with a decreasing stack. Then we flip the comparison and the direction to get the other variants, and store indices to measure distances.
Definition
A monotonic stack is a stack whose contents stay sorted, increasing or decreasing from bottom to top, because each push first pops every element that would break the order. Each popped element has found its answer, the next greater or next smaller element, which is the one being pushed. Every element is pushed and popped at most once, so processing elements takes time.
In this topic
- 1Next Greater Element
- 2Monotonic Stack Variants
Next Greater Element
The next greater element of is the first with and , or -1 if none exists. Scan left to right, keeping a stack of the elements that have not yet found theirs. For each new value x, pop every stack element smaller than x and record x as its answer, then push x. Whatever remains at the end has no greater element to its right. The stack stays decreasing, since everything smaller than x was just removed. Each element is pushed once and popped at most once, so at most 2n stack operations happen in total, even though a single step may pop many elements.
To find the next greater element for each array position, scan left to right maintaining a decreasing stack of indices. For each new element : pop all stack-top elements smaller than (their answer is ), then push . Each index is pushed and popped at most once, giving amortized per element and total — a rare case of linear time for what appears to be an problem.
Find the next greater element for each value in [4, 5, 2, 10, 8].
Monotonic Stack Variants
Two choices define the variant. The comparison decides what is found: a decreasing stack, which pops smaller elements, finds the next greater element, while an increasing stack, which pops larger elements, finds the next smaller one. The direction decides the side: scanning right to left, or reading the stack top just before pushing, gives the previous greater or smaller element instead. Storing indices rather than values lets the answer be a distance, the current index minus the popped index. Whether equal values pop is also a choice, and it decides which duplicate wins. These variants underlie Daily Temperatures, Largest Rectangle in Histogram, and Trapping Rain Water.
Four variants by direction and comparator: next/previous greater/smaller. All share the invariant that popped elements have their answer determined and the stack remains sorted in one direction. The same amortized bound applies. These stacks power problems like largest rectangle in histogram, trapping rain water, and daily temperatures.
For temperatures [73, 74, 75, 71, 69, 72, 76, 73], how many days until warmer?
A trading dashboard shows the highest price over the last k minutes, updated every minute. Recomputing the maximum over all k values costs over n updates, 3,600 comparisons per update for a one-hour window of per-second prices. Unlike a running sum, a running maximum cannot simply subtract the value that leaves: if the departing value was the maximum, the new one is unknown.
The previous topic, Monotonic Stack, kept only the elements still waiting for an answer and discarded the rest. Sliding Window Maximum applies the same pruning to a moving window. If a newer value is at least as large as an older one, the older one can never again be the maximum: it leaves the window first, and until then the newer value beats it. Such dominated values can be thrown away as soon as the newer one arrives. What remains is decreasing, with the maximum at the front, and expired indices leave from the front as the window slides. A deque removes from both ends in constant time.
We first build the deque algorithm and trace it. Then we compare it with a heap and see where the heap wastes work.
Definition
Given an array of length and a window size , the sliding window maximum is the sequence of maxima of every contiguous window of length , which gives values. A deque of indices whose values decrease from front to back computes all of them in time and extra space. Each index enters and leaves the deque at most once.
In this topic
- 1Deque-Based Maximum Tracking
- 2Why Not a Heap?
Deque-Based Maximum Tracking
The deque holds indices whose values strictly decrease from front to back. For each new index i with value x, do three steps. First, pop from the back every index whose value is at most x, since x outlives and outranks them. Second, append i. Third, if the front index is at most i - k, it has left the window, so pop it from the front. Once i reaches k - 1, the front holds the window maximum. Each index is appended once and popped at most once, so the total is . Store indices, not values, or expiry cannot be checked.
Maintain a deque of indices with values decreasing from front to back. For each new element : pop from the back while is larger (they can never be the max now), push , then pop from the front if it is out of window. The front is always the current window max. Each index enters and leaves the deque at most once, so total time is .
Find sliding window maximums for [1, 3, -1, -3, 5, 3, 6, 7] with k=3.
Why Not a Heap?
A max-heap reads its largest value in and inserts in , but it cannot cheaply remove an arbitrary element, and the value leaving the window may sit anywhere inside it. The usual workaround is lazy deletion: push (value, index) pairs, and before reading the top, pop entries whose index has left the window. That is correct, but stale entries buried below the top are never removed, so the heap can grow to entries, each operation costs , and the total is . The deque discards dominated values eagerly and never holds more than k indices.
A max-heap can track the window max in per insert but cannot efficiently delete arbitrary out-of-window elements, so it requires lazy deletion. With lazy deletion heap operations become amortized, giving total — worse than the deque's . The deque works because monotonicity lets us discard dominated elements eagerly instead of lazily.
What is the time complexity of the deque approach vs. heap approach?
Related Problems on PixelBank
Sorting a million numbers by repeatedly picking the smallest takes about comparisons, some 500 billion of them. Multiplying two 1000-digit numbers digit by digit takes a million digit products. Both methods attack the whole input at once, and each element ends up compared or multiplied with far too many others.
The previous topic, Sliding Window Maximum, won by throwing away values that could never matter. Divide and conquer wins differently: split the input into smaller instances of the same problem, solve each recursively, and combine the answers. Merge sort splits an array in half, sorts both halves, and merges them in linear time, reaching . Binary search keeps only one half. Unlike dynamic programming, the pieces do not overlap, so no memo table is needed. The running time follows a recurrence, the number of pieces times the cost of one piece plus the cost of combining, and the Master Theorem solves most such recurrences by comparing the combine cost with the growth in the number of subproblems.
We first state the template and apply the Master Theorem. Then we solve maximum subarray twice, by divide and conquer and by Kadane's linear scan.
Definition
Divide and conquer solves a problem of size by splitting it into independent subproblems of size , solving them recursively down to a base case, and combining the results in time, so . With , the Master Theorem gives if , if they are equal, and otherwise. Unlike dynamic programming, the subproblems do not overlap, so their answers are never reused.
In this topic
- 1The Divide-and-Conquer Template
- 2Maximum Subarray (Kadane's Algorithm)
The Divide-and-Conquer Template
Every divide-and-conquer algorithm has three steps. Divide the input into subproblems, each of size . Conquer each one recursively, stopping at a base case small enough to solve directly. Combine the sub-answers, at a cost of . The recursion tree has levels, and level holds subproblems. If combining dominates, when , the root's work wins and . If the two balance, every level costs the same and . If subproblems multiply faster than they shrink, the leaves dominate and .
Divide into subproblems of size , combine in time. The Master Theorem gives: if , total is ; if , ; if , . Merge sort is : . This template subsumes many classical algorithms including FFT and matrix multiplication.
Merge sort splits into 2 subproblems of size n/2, with O(n) merge cost. What is its complexity?
Maximum Subarray (Kadane's Algorithm)
Maximum subarray asks for the contiguous run with the largest sum. Divide and conquer splits at the midpoint: the best run lies in the left half, in the right half, or crosses the middle. The crossing case is the best suffix of the left half plus the best prefix of the right, found in linear time, so . Kadane's algorithm is linear. Let cur be the best sum of a run ending at index i: either extend the previous run or start fresh, so cur = max(a[i], cur + a[i]). The answer is the largest cur seen. Initialize best to a[0], not 0, or an all-negative array returns 0.
Kadane maintains subarray ending at , with recurrence . Final answer is . This is , better than the divide-and-conquer approach. The key insight is that an optimal subarray ending at either starts fresh at or extends the best one ending at .
Find the maximum subarray sum of [-2, 1, -3, 4, -1, 2, 1, -5, 4].
A lecture hall takes bookings, and the manager wants to accept as many talks as possible without any two overlapping. Trying every subset of n requests takes checks, which is hopeless beyond a few dozen requests. Simple rules, such as shortest talk first or earliest start first, are tempting, but most fail on some input.
The previous topic, Divide and Conquer, split a problem into independent parts and combined their answers. A greedy algorithm neither splits nor looks back: it makes the choice that looks best right now, commits to it, and solves what is left. For the hall, the right rule is to pick the talk that ends first, because that leaves the most time for the others. Greedy algorithms are fast, often a sort plus a scan, but their correctness must be proved, since a plausible rule can be wrong. The standard proof is an exchange argument: any optimal solution can be changed step by step into the greedy one without getting worse.
We first study the greedy-choice property and its exchange proof on activity selection. Then we solve Jump Game by tracking the farthest reachable index.
Definition
A greedy algorithm builds a solution by repeatedly making the choice that is best under a local rule and never revising it. It is correct when the problem has the greedy-choice property, meaning some optimal solution contains the greedy first choice, and optimal substructure, meaning the rest of that solution is optimal for the subproblem that remains. Correctness is usually shown by an exchange argument.
In this topic
- 1Greedy Choice Property
- 2Jump Game
Greedy Choice Property
The greedy-choice property says that some optimal solution begins with the greedy choice. Proving it uses an exchange argument. Take any optimal solution; if its first choice differs from the greedy one, swap in the greedy choice and show that the result is still valid and no worse. For activity selection, let g be the activity that ends first. An optimal schedule's first activity ends no earlier than g, so replacing it with g keeps every later activity compatible and leaves the count unchanged. Repeating this on the activities after g proves the schedule optimal. Without such a proof, a greedy rule is a guess.
A greedy algorithm is correct iff the problem has the greedy choice property: a locally optimal choice at each step leads to a globally optimal solution. Formal proof usually proceeds by an exchange argument: assume an optimal solution differs from the greedy choice, swap, show the solution is still optimal. Matroid theory generalizes this: greedy works on any weighted matroid.
In the activity selection problem, why does choosing the earliest-finishing activity work?
Jump Game
Each nums[i] is the maximum jump length from index i; can the last index be reached? Searching jump sequences is exponential, and DP over indices is . Greedy tracks one number, reach, the farthest index reachable so far. Scan left to right. If i > reach, index i is unreachable, and so is everything beyond it, so return False. Otherwise set reach = max(reach, i + nums[i]), and return True once reach covers the last index. This is safe because every index up to reach is reachable, so the best jump from any of them is available. Jump Game II reuses the scan.
Maintain = farthest index reachable so far. Scan left to right: if , return false (unreachable); else update . Time . Correctness: at each step, we greedily extend reach to the maximum — any farther index reachable at all must come through a reachable index we have already processed.
Can you reach the end of [2, 3, 1, 1, 4]?
Related Problems on PixelBank
A company has a day of meeting bookings and must know how many conference rooms to reserve, which is the largest number of meetings running at the same moment. Checking every pair of meetings for overlap costs comparisons and still leaves the real question open: how many of them are in progress at once? Counting overlapping pairs does not answer it.
The previous topic, Greedy Algorithms, sorted activities by end time and made a single pass. Line sweep also sorts and makes a single pass, but over events instead of objects. Imagine a vertical line moving left to right across time. Each meeting contributes two events: a start, where the running count goes up by one, and an end, where it goes down by one. Between events nothing changes, so the count only needs checking at the 2n event points, and the largest count seen is the peak. With a balanced tree in place of the counter, the same idea finds intersecting segments in geometry.
We first turn objects into sorted events and sweep them with a counter. Then we solve Meeting Rooms, where the tie-break at equal times decides the answer.
Definition
A line sweep solves a geometric or scheduling problem by moving an imaginary line across one axis and processing events, the points where the answer can change, in sorted order. A running state, such as a counter or a balanced tree of active objects, is updated at each event and queried between them. Sorting the events dominates, so a sweep over intervals costs time.
In this topic
- 1Event-Based Processing
- 2Meeting Rooms: Minimum Rooms Needed
Event-Based Processing
Each object becomes events at the coordinates where it starts and stops mattering: an interval [s, e] yields (s, +1) and (e, -1). Sort all events by coordinate, then walk through them while maintaining a running state, here the number of active intervals. Between consecutive events the state is constant, so checking it only at events is exact, and the largest running count is the maximum overlap. Ties matter: at equal coordinates, the order of ends and starts decides whether touching intervals count as overlapping. For richer questions the state becomes a set or balanced tree of active objects, as in segment intersection.
Convert each interval into two events: start and end. Sort all events by coordinate (break ties with starts before ends or vice versa depending on the problem). Sweep left to right maintaining a running sum — the maximum value of the running sum is the maximum overlap. Time dominated by the sort.
Three meetings: [0,30], [5,10], [15,20]. How many rooms are needed?
Meeting Rooms: Minimum Rooms Needed
Meeting Rooms II asks for the fewest rooms that can host every meeting. Meetings are half-open, [start, end), so a room freed at time t can host a meeting starting at t. The answer equals the largest number of meetings in progress at once. Sweep the events with one rule for ties: at equal times, process -1 before +1. Sorting (time, delta) tuples does this automatically, because -1 sorts before +1. An equivalent method sorts the starts and the ends separately and walks both lists with two pointers. A third keeps a min-heap of end times and reuses the room that frees earliest, which also assigns each meeting a room.
The minimum rooms needed equals the maximum point overlap across all meetings. Using events: sort events, sweep, track running concurrent meetings. Alternative: sort starts and ends separately, use two pointers to count overlaps. Both are . This same technique answers max-concurrent queries for any interval collection.
Meetings: [[0,30],[5,10],[15,20],[10,15]]. Minimum rooms?