The previous chapter focused on problems where at least one signal points to a known pattern. But some problems do not give you that clue. The input type, constraints, and wording may not clearly map to anything familiar.
This chapter covers what to do in that situation. The goal is to keep making progress when the starting point is unclear, without depending on recognizing the pattern upfront.
A problem can feel new while its underlying structure is one a candidate has already practiced. Different wording, an unfamiliar setting, and an unusual phrasing of the constraints can hide a structure that maps cleanly to a known pattern.
Consider this problem:
You are a gardener with a row of flower pots. Each pot can hold one flower. You want to plant flowers such that no two flowers are adjacent.
Given an array where 1 means a flower is already planted and 0 means the pot is empty, and an integer n, determine if you can plant n new flowers without violating the rule.
At first, this reads like a domain-specific scenario.
If you strip away the story:
What remains is a greedy array problem with adjacency constraints.
Problem statements often change:
But what usually stays the same:
When the algorithm is not obvious, start by solving a concrete example by hand without trying to invent a general algorithm first. Walking through the example step by step exposes the implicit decisions, and those decisions are usually what the algorithm needs to encode.
Given an array of non-negative integers representing the height of bars, compute how much water can be trapped between them after rain.
Set the algorithm aside and reason about each position individually: how much water can sit at this index, given the heights around it?
A rule emerges from working the positions one by one: the water at each index equals the shorter of the tallest bars on its left and right, minus the bar's own height, clamped at zero. Recognizing the problem as a known "trapping rain water" question is not required for the rule to surface; it falls out of the manual trace itself.
A problem that looks complex at full size often becomes tractable when reduced to its smallest non-trivial cases. Working those cases out by hand surfaces the structure of the answer, which then generalizes back to the original input.
Work through increasing input sizes and observe what changes:
A message containing letters A-Z is encoded: 'A' = 1, 'B' = 2, ..., 'Z' = 26. Given a string of digits, return the number of ways to decode it.
The recurrence becomes visible once the smallest cases are written out:
Working through "1234" gives the rule: at each position, the next character can be taken alone (decode the rest) or combined with the following character (if the pair is between 10 and 26, decode what follows). The number of ways for "1234" is the number of ways for "234" plus the number of ways for "34".
That is a recurrence: ways(i) = ways(i+1) + ways(i+2) whenever the two-digit number is valid. Walking through n = 0 through n = 4 by hand turns an opaque counting problem into a familiar dynamic programming structure analogous to climbing stairs.
Reducing input size strips away the bookkeeping that hides the underlying rule. With the bookkeeping gone, the recurrence or invariant becomes easier to spot, and the general solution then reads as an extension of the smaller cases rather than a fresh insight.
Problems involving position, ordering, movement, overlap, or connections often have a structure that is much easier to see in a picture than in prose. A quick sketch tends to converge on the algorithm faster than further reading of the statement.
| Problem Type | What to Visualize |
|---|---|
| Array problems | Draw the array with indices, annotate with arrows showing pointer movement |
| Tree problems | Draw the actual tree, label nodes, trace the traversal path |
| Graph problems | Draw nodes and edges, mark visited nodes, show traversal order |
| Interval problems | Draw intervals on a number line, show overlaps |
| Matrix problems | Draw the grid, shade cells, show movement directions |
| Linked list problems | Draw nodes with arrows, show how pointers change |
Given a collection of intervals, merge all overlapping intervals.
Draw these on a number line:
The overlap reads off the picture directly. [1,3] and [2,6] share a region, so they merge into [1,6]. The other two intervals sit on their own, giving the final result [[1,6], [8,10], [15,18]].
Once the picture is clear, the algorithm follows:
The same technique helps with debugging. Sketching the state at each step and comparing it against what the code actually produces tends to surface off-by-one and pointer mistakes faster than re-reading the code.
This idea comes up often, but it is especially useful when nothing else is working. A brute force approach may not be efficient, but it gives you a clear and correct starting point.
A simple way to begin is to ask: "If I had unlimited time and computing power, how would I solve this?"
This gives you a baseline solution you can build on.
Given an integer array, find the length of the longest strictly increasing subsequence.
Brute force approach:
There are 2ⁿ subsequences, so the brute force runs in O(2ⁿ) time. It is not practical for realistic input sizes, but it is correct and easy to reason about.
While working through brute force, a pattern starts to appear.
You keep asking:
i?And you notice that this question depends on answers to the same question at earlier indices.
This is a sign of overlapping subproblems, which leads to dynamic programming.
Define:
dp[i] = length of the longest increasing subsequence ending at index iFor each i, check all j < i:
nums[j] < nums[i], then dp[i] = max(dp[i], dp[j] + 1)This reduces the complexity to O(n²).
The complexity drops further to O(n log n) using a patience-sorting style approach: maintain an auxiliary array where the position k stores the smallest tail value seen so far for any increasing subsequence of length k+1. For each new number, binary-search for the leftmost position in that array whose value is greater than or equal to the new number and replace it (or append if the number is larger than all values seen). The length of the auxiliary array at the end is the answer.
A brute-force-first progression is useful even when the optimal solution does not surface in time. It produces a correct baseline, makes the bottleneck explicit, and turns optimization into a series of incremental changes rather than a single leap from blank page to optimal solution.
Many large problems decompose cleanly into smaller, individually-solvable pieces. Solving the pieces and composing them is usually easier than solving the whole problem at once.
When you feel stuck, ask:
Given a string s and a string p, find all start indices of p's anagrams in s.
Decomposing this problem produces three familiar pieces:
s with the same length as p and the same character frequencies.s? A sliding window of size len(p).Combined, the algorithm slides a window of size len(p) over s, maintains a frequency count of the window, and compares it against the frequency count of p. Each piece (frequency counting, sliding window, comparison) is a routine subproblem.
The problem splits cleanly into two routine tree operations:
#) for null children.Both halves are standard tree traversals. The full problem is the composition of the two.
Plenty of interview problems are familiar patterns wrapped in unfamiliar wording. The underlying data structure, operation, and constraint usually map to something well-known once the scenario is stripped away.
Any time you have a 2D grid where you move between adjacent cells, it is essentially a graph problem. Each cell is a node, and each valid move is an edge. "Number of islands" is just counting connected components. "Shortest path in a maze" is BFS on a graph.
When the problem asks for the "number of ways" or "number of distinct results," it often points to dynamic programming.
These usually involve breaking the problem into smaller subproblems and combining results.
Nested structures often behave like trees. For example, evaluating expressions with parentheses can be seen as traversing an implicit tree.
Whenever you see nested structures, matching pairs, or processing from inside out, a stack is often involved.
Given a list of meeting time intervals, find the minimum number of conference rooms required.
The problem reads like scheduling, but reframing it as "find the maximum number of meetings overlapping at any point in time" turns it into the classic maximum-overlap problem solvable by a sweep line. Sort start times and end times into separate arrays, sweep through both in order, increment a counter on each start and decrement on each end, and track the maximum value the counter reaches. That maximum is the number of rooms required. The conference-room framing is the disguise; the underlying pattern is sweep line over events.
In a grid, a fresh orange becomes rotten if it is adjacent to a rotten orange (each minute, rotting spreads). Return the minimum number of minutes until no fresh orange remains, or -1 if impossible.
The simulation framing hides a shortest-distance question. Each rotten orange spreads exactly one step per minute, which is the definition of multi-source BFS: start the queue seeded with every initially rotten orange, expand in lockstep, and the number of BFS levels until the queue empties is the answer. The orange framing is the disguise; the pattern is multi-source BFS for shortest distance.
When pattern matching does not produce a candidate approach, the techniques in this chapter can be applied in order. Each one approaches the problem from a different angle, and going through them sequentially usually surfaces enough structure to start coding.
The underlying principle is forward progress. Working an example, simplifying the input, drawing the structure, or writing a brute-force solution each yields concrete state that the next step can build on. The optimal solution does not need to appear on the first attempt; moving from the blank page to something tractable is what matters.