Cracking the Coding Interview by Gayle Laakmann McDowell

Cracking the Coding Interview

7.2/106 min read

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