
Cracking the Coding Interview
Gayle Laakmann McDowell
4/27/16
The ability to communicate thoughts and ideas, in a clear, concise manner, is an absolutely invaluable skill that employers seek
It’s better to reject a good candidate than hire a bad one
One of the most important messages that you, as a candidate, can convey in your interview is hiring me will make your lives easier. They can rely on you. You’re able to figure things out on your own
Must-have knowledge: data structures (linked lists, binary trees, tries, stacks, queues, arrays, hash tables), algorithms (breadth first search, depth first search, binary search, merge sort, quick sort, tree insert / find), concepts (bit manipulation, singleton design pattern, factory design pattern, memory (stack vs heap), recursion, big-o time)
5 steps to solving a technical question: 1. Ask questions to resolve ambiguity. 2. Design an algorithm. 3. Write pseudo-code. 4. Write code. 5. Test your code
Test for extreme cases (0, negative, null, maximums), user error (wrong input), and general cases
5 algorithm approaches: 1. Examplify (write out specific examples and see if you can figure out a general rule). 2. Pattern matching (what problems is the algorithm similar to, can you modify the solution for this problem?). 3. Simplify and generalize (change a constraint to simplify the problem, once you have the algorithm, generalize the problem). 4. Base case and build (solve for a base case, then harder). 5. Data structure brainstorm (go through data structures and try to apply each one)
Recursive problems: Solve for the 'base case.'' Solve for f(0) or f(1) and then solve it for f(2). Understand how to solve for f(3) using previous solutions. Generalize for f(n).
Every recursive problem can be solved linearly. Recursive takes up more space, iterative is more complex
During recursion, we can add 1 to each recursive call
Stacks and queues can be useful for recursive algorithms
Bottom-up: solve for simple case (one element) then figure it out for two elements. Build the solution for one case off the previous case. Uses
Top-down approach: divide the problem for case N into subproblems
Recursive runtime: O(branches^depth)
Bubble Sort: Start at the beginning of an array and swap the first two elements if the first is bigger than the second. Go to the next pair, etc, continuously making sweeps of the array until sorted. O(n^2)
Selection Sort: Find the smallest element using a linear scan and move it to the front. Then, find the second smallest and move it, again doing a linear scan. Continue doing this until all the elements are in place. O(n^2)
Merge Sort: Sort each pair of elements. Then, sort every four elements by merging every two pairs. Then, sort every 8 elements, etc. O(nlogn)
Quick Sort: Pick a random element and partition the array. Then do that for each half, then each quarter, etc. O(nlogn)
Bucket Sort: Partition the array into a finite number of buckets, and then sort each bucket individually. O(n +m), where n is the number of items and m is the number of distinct items
Permutation = anagram = same letter in a different order
Palindrome = same order back and forth
Stack: LIFO, push, pop; Queue: FIFO, push, shift
Queues are used for breadth-first search or implementing a cache
Min-heap: complete binary tree, each node is smaller than its children, root is the minimum
Insert: insert at right most bottom and bubble up
Extract min: always at the top, remove top and swap with bottom most right node and bubble down
Robin-Karp substring search: treat strings as base 128 numbers, use hash functions to sum chars, the same strings would have the same hash value
A*: search all paths for the least cost
Ford-Fulkersons: max flow in a flow network
Knapsack: set of items with weights, get the number of items that fit in a collection
Dijkstras: graph with edges with weights, find the shortest path between nodes, set all node path weights to infinity
Hamiltonian: find the shortest path to visit each node once
Clique: find a subset of vertices all adjacent to one another
Vertex cover: set of vertices that touch all edges in a graph
Traveling salesman: given a list of cities and distances, find the shortest path to every city and return
IsNaN('123') -> false
Powers of 2: 2^10 = 1024 = 1KB, 2^20 = 1 MB, 2^30 = 1 GB, 2^34 = 4GB, 2^40 = 1TB
Closure: wrap JS in a function, avoids potential name clashing
CSS pseudo classes: hover, activated, focus, child, before, visited
Window.onLoad = scripts and images are ready, document.onLoad = scripts are ready, images are not
DOM: getElementById, getElementByClassName, getElementByTagName, querySelectorAll
CSS: element.classList.add(className), .remove(className), .toggle(className), .contains(className)
Problem solving steps: 1. Listen. 2. Debug example. 3. Special cases. 4. Brute force. 5. Optimize. 6. Walk through optimized solution. 7. Clean up code. 8. Test. 9. Test with special cases
Binary search tree: all left descendents <= n < all right descendents
AVL trees and red-black trees are balanced trees
A complete binary tree means every level is fully filled besides the last which is filled left to right
Full binary tree: every node has either 0 or 2 children
Perfect binary tree: both full and complete
In-order traversal: visit left branch, then current node, then right branch
Pre-order traversal: visit current node, then left branch, then right branch
Depth first search: go deep first before we go wide
Breadth first search: we explore each neighbor before any of their children. Better for finding the shortest path. Not recursive, uses a queue
Singleton pattern ensures that a class has only one instance and ensures access to that instance through the application
Dynamic programming: find overlapping subproblems of a recursive algorithm and cache those results for future calls
Use an offset while mapping over a matrix and board
Use the runner technique or a second pointer when working with linked lists
Beautiful code: modularize, error checks, use classes/structs/enums, good variable names
Array and string algorithm ideas: sorting first or last, do a sweep first to gather information, precompute variables to be used later on, error checking first, iterate forward or backward, keep track as you go along
Linked list algorithm ideas: use a while loop to iterate over a linked list, use a runner pointer which can move around ahead of the main pointer, calculate the length of the list first using a runner
Stack and queue algorithm ideas: stack objects to hold additional values besides a value
Tree and graph algorithm ideas: Use depth-first search with recursion, breadth-first search without, in-order traversal of a binary search tree creates a sorted array, pass down the min and max values as you traverse a binary search tree, write submethods to perform detailed calculations we run often
Recursion and dynamic programming algorithm ideas: keep a structured result that we can pass down to each recursive call, create helper functions to do recurring calculations
The most efficient code is not always best, your ability to maintain and read code matters
