You are on page 1of 2

First, I read the problem and gain an understanding of what it is asking.

Next, I come up with example inputs and outputs on a whiteboard. Going


through examples tests that I understand the problem, and it gives me an
opportunity to see how I might solve the problem. It also gives me an
opportunity to come up with edge cases early. During this time, I ask myself
about the ranges of input and output values; whether values can be missing,
empty, or null; whether overflow is something I need to be careful about;
whether values can be repeated; etc.
After reading the problem, if the correct approach is not immediately obvious,
I will probably start thinking about a brute force approach. This usually is a
direct translation of what I did to solve the problem by hand.
Once I have a brute force approach in mind, I will try to identify the
bottlenecks in the brute force solution: the parts of the algorithm that have a
less than optimal run-time. To identify bottlenecks, you must break the brute
force problem into different parts and evaluate the run-time of each
individual part. It is also helpful to have an intuition about what the best
conceivable runtime will be. Sometimes your guess of a best conceivable runtime will be an overestimate or underestimate. Prepare yourself for this by
keeping an open mind.
The bottlenecks of a brute force solution will dictate what I do next. The
simplest way to overcome a number of bottlenecks is to apply a data
structure. To figure out which data structure to use, I ask myself what
operations I need to support efficiently. Those operations will dictate the data
structure. For example, if I need to quickly find values in a collection by some
key, I will consider hash tables and BSTs. To be most effective, you need a
good understanding of data structures, the operations they support, and the
efficiency of those operations.
o The data structures I typically consider, in order of most common to
least common, include:
Arrays / Array lists
Linked lists / Queues / Stacks / Circular Buffers
Hash maps / Hash sets / Bit sets
Balanced binary search trees
Priority queues / Heaps
Tries
Disjoint sets
Segment trees
Fenwick trees
Order-statistic trees
Suffix trees
Sometimes applying a data structure will not help you overcome the
bottleneck. In these circumstances, I might consider applying two data
structures (e.g., a queue and a stack, two heaps, or two stacks). Two data
structures are common when you want to support amortized operations (e.g.,
a queue with a max() method) or when you want to maintain a partition in

the data (e.g., smaller elements and larger elements or used elements and
unused elements).
Sometimes data structures themselves will not be very helpful for
overcoming bottlenecks. If this is the case, then you might want to consider
algorithmic techniques.
o The algorithmic techniques I typically consider, in order of my common
to least common, include:
Preprocessing
Sorting
Hashing
Binary search
Recursion
Sliding window / caterpillar method
Rolling hash
Dynamic programming / memoization
Divide and conquer
Square root decomposition
Pruning / backtracking
Math
Partitioning a range / Bucketing
Rewriting a problem as the difference of two sub problems
Sieving
Lazy evaluation
Amortization
Randomization
Greedy evaluation
Coordinate compression
Tortoise and hare pointer manipulation (slow and fast pointer)
Solving backwards (from expected output to input)
Solving for the complement of the answer and inverting the
result
Isolating concerns (e.g., solving for each dimension separately in
a 2D space problem)
If it is still unclear how to overcome the bottleneck, it might help to think of
ways to rephrase the problem. Try to brainstorm slightly different problems
whose solution will allow you to produce the solution to the original problem.
In order to rephrase a problem, break it down into its smallest parts and then
try breaking it down a different way. Maybe you can solve the problem with
Operation A and Operation B, but maybe you can also solve the problem with
Operation C and Operation D. Unfortunately, this step is the least structured
approach of the above steps, so it can end up being a giant time killer.

You might also like