Solution Steps
- Listen and pay attention to the problem description
- Record every piece of unique or potentially useful information. Likely needed for optimal solution
- Ask questions to understand problem
- Repeat the problem as you understand it out loud
- Ask questions about the part you think will be hardest. You might be understanding it incorrectly and might be easier than you think or not need to address it
- Ask questions about the data: types, what the data presents, what it’s meant for, who’ll be using it, etc.
- Create an example that’s representative, not too small and not a special case
- Develop a brute force solution and share it. Don’t write code, just talk through it or write it in comments
- Design an algorithm
- If the answer isn’t obvious, look for a simpler variation of the problem.
- Remove one of the constraints or multiple
- Think about any subproblems you can solve and start from there
- Does your algorithm leverage all available information or did you disregard or overlooking something useful?
- What’s the space and time complexity?
- What happens if there’s a lot of data?
- What happens if there’s no data or wrong data?
- Does this design create other issues?
- Did you make the right trade-offs? For which scenarios might the trade-offs be less optimal?
- If the answer isn’t obvious, look for a simpler variation of the problem.
- Walk through the optimal approach in detail before you start coding. Make sure you understand each step
- Write code at a moderate pace
- Write pseudocode or comments first. Make sure to tell the interviewer you’ll write real code after
- Write the code. Modularize and refactor as you go. Objective is to write good clean code
- Use data structures generously. This shows you’re comfortable with them and care about good OOP design
- When writing on whiteboard or paper, start from upper lefthand corner to not crowd your code
- Optimize using BUD Optimization
- Test the code
- Conceptually walk through the code like in a detailed code review
- Test happy paths
- Target unusual or non-standard code like loop indices that may be off by one
- Hot spots like arithmetic and null nodes, base cases in recursion, all integer divisions to see if they need rounding or could cause division exceptions, etc.
- Small test cases
- Special cases and edge cases
- Extreme cases: 0, negative, null, maximums, minimums
- User error: wrong input, wrong usage
- When you find a bug, think first then act
- Don’t willy nilly trying something. You don’t want to be seen as a random fixer and it creates more issues down the line
Solution Guidelines
Clean Code
- Correct: operate correctly on expected and unexpected inputs.
- Efficient: operate as efficiently as possible in terms of time and space. This includes Big O and real-life efficiency. A constant factor might get dropped when you use Big O but in real life, it can matter.
- Simple: If you can do something in 10 lines instead of 100, you should.
- Readable: A developer should be able to read your code and understand what it does and how it does it.
- Maintainable: Adaptable to changes and easy to maintain by others.
It’s advisable to sacrifice some degree of efficiency to make code more maintainable, and vice versa. Ask when in doubt.
Good Code
- Use Data Structures Generously
- Think of all the possible data structures you can use for the solution
- Sometimes it’s better to create your custom data structure, like a struct or a class or an enum
- Code Reuse
- Try to reuse the same modules or functions for similar or variants of the same problem
- Modular
- Use modules or functions to separate code with different concerns and functions
- Flexible and Robust 2. Code should be flexible to different constraints. For example, don’t assume board size in a board problem 3. Use of generics or templates when appropriate 4. Using constants instead of variables or vice versa when appropriate
- Error Checking
- You can skip validations in an interview
- Ask the interviewer if we need to validate the input or can assume it’s valid
- Tell the interviewer you would normally validate input and do it in x and y ways but for time you won’t do it immediately but at the end after the problem is solved
- If statements
- Assert statements
- You can skip validations in an interview
Speed and skipping
- Skip initialization code and obvious boilerplate and replace it with an assumed function call that does the job for you
- Use todos in place of things that aren’t core to the algorithm like error checking, validation, and testing. Come back to them later if the interviewer likes
Five Problem Approaches
Exmaplify
- Write out specific examples of a problem
- See if you can derive a general rule from the examples
Example
Given a time, calculate the angle between the hour and minute hands.
Solution
- Let’s start with an example like 3:27.
- We can draw a picture of a clock by selecting where the 3 hour hand is and where the 27 minute hand is.
- For the below solution, we’ll assume that h is the hour and m is the minute.
- We’ll also assume that the hour is specified as an integer between 0 and 23, inclusive.
- By playing around with these examples, we can develop a rule:
- Angle between the minute hand and 12 o’clock: 360 * m / 60
- Angle between the hour hand and 12 o’clock: 360 * (h % 12) / 12 + 360 (m / 60) * (1 / 12)
- Angle between hour and minute: (hour angle - minute angle) % 360
- By simple arithmetic, this reduces to (30h - 5.5m) % 360.
Pattern Matching
- Think about what problems this problem is similar to
- Write out the solution to the other problem
- Modify it to develop the solution for this problem
Example
A sorted array has been rotated so that the elements might appear in the order 3, 4, 5, 6, 7, 1, 2. How would you find the minimum element?
You may assume that the array has all unique elements.
Solution
There are two problems that jump to mind as similar:
- Find the minimum element in an array.
- Finding the minimum element in an array isn’t an interesting algorithm (iterate through all the elements), nor does it use the information provided (that the array is sorted). It’s unlikely to be useful here.
- Find a particular element in a sorted array (i.e., binary search) 2. Binary search is applicable. You know that the array is sorted, but rotated. So, it must proceed in an increasing order, then reset, and increase again. 3. The minimum element is the “reset” point. 4. If you compare the middle and last element (6 and 2), you will know the reset point must be between those values, since MID > RIGHT. 5. This wouldn’t be possible unless the array “reset” between those values. 6. If MID < RIGHT, then either the reset point is on the left half, or there is no reset point (the array is really sorted). 7. We can continue to apply this approach, dividing the array in half like binary search. We will eventually find the minimum element (or the reset point).
Simplify and Generalize
- Change a constraint such as the data type or amount of data. This helps simplify the problem.
- Solve the new simplified version of the problem.
- Once we have an algorithm for the simplified problem, we generalize the problem and try to adapt the solution for the more complex version
Example
A ransom note can be formed by cutting words out of a magazine to form a new sentence. How would you figure out if a ransom note (represented as a string) can be formed from a given magazine (string)?
Solution
- Modify it so that we are cutting characters out of a magazine instead of whole words.
- Solve the simplified problem with characters
- Creating an array
- Counting the characters
- Each spot in the array corresponds to one letter
- Count the number of times each character in the ransom note appears
- Go through the magazine to see if we have all of those characters.
- Generalize the algorithm
- Rather than creating an array with character counts, we create a hash table that maps from a word to its frequency
Base Case and Build
This often lead to natural recursive algorithms.
- Solve the problem first for a base case (e.g.,
n = 1
). This usually means recording the correct result. - Try to solve the problem for
n = 2
, assuming that you have the answer forn = 1
. - Try to solve it for
n = 3
, assuming that you have the answer forn = 1
andn = 2
. - Build a solution that can always compute the result for
n
if we know the correct result forn-1
. - It may not be until
n
equals 3 or 4 that we get an instance interesting enough to try to build the solution based on the previous result.
Example
Design an algorithm to print all permutations of a string. For simplicity, assume all characters are unique. Consider a test string “abcdefg”
Solution
- Case “a” ⇒ {“a”}
- Case “ab” ⇒ {“ba”}
- Case “abc” ⇒ ?
- This is the first “interesting” case. If we had the answer to P(“ab”), how could we generate P(“abc”)?
- Additional letter is “c,” so we can just stick c in at every possible point.
- P(“abc”) = insert “c” into all locations of all strings in P(“ab”)
- P(“abc”) = insert “c” into all locations of all strings in {“ab”, “ba”}
- P(“abc”) = merge({“cab”, “acb”, “abc”}, {“cba”, “bca”, bac”})
- P(“abc”) = {“cab”, “acb”, “abc”, “cba”, “bca”, bac”}
- Additional letter is “c,” so we can just stick c in at every possible point.
- This is the first “interesting” case. If we had the answer to P(“ab”), how could we generate P(“abc”)?
- Develop a general recursive algorithm.
- Generate all permutations of a string s₁…s by “chopping off” the last character and generating all permutations of s₁…Sn-1
- Once we have the list of all permutations of S1..Sn-1 we iterate through this list
- For each string in it, we insert s, into every location of the string
Data Structure Brainstorm
- Run through a list of data structures and try to apply each one.
Example
Numbers are randomly generated and stored into an (expanding) array. How would you keep track of the median?
Solution
- Linked list? Probably not. Linked lists tend not to do very well with accessing and sorting numbers.
- Array? Maybe, but you already have an array.
- Could you somehow keep the elements sorted? That’s probably expensive. Let’s hold off on this and return to it if it’s needed.
- Binary tree? This is possible, since binary trees do fairly well with ordering.
- If the binary search tree is perfectly balanced, the top might be the median.
- If there’s an even number of elements, the median is actually the average of the middle two elements.
- The middle two elements can’t both be at the top.
- This is probably a workable algorithm, but let’s come back to it.
- Heap? A heap is really good at basic ordering and keeping track of max and mins.
- If you had two heaps, you could keep track of the bigger half and the smaller half of the elements.
- The bigger half is kept in a min heap, such that the smallest element in the bigger half is at the root.
- The smaller half is kept in a max heap, such that the biggest element of the smaller half is at the root.
- You have the potential median elements at the roots.
- If the heaps are no longer the same size, you can “rebalance”the heaps by popping an element off the one heap and pushing it onto the other.