├── .gitignore ├── README.md ├── code ├── .DS_Store ├── javascript │ ├── .DS_Store │ ├── binary-search-tree.js │ ├── ctci.js │ ├── graphs.js │ ├── heap.js │ ├── linked-list.js │ ├── lru.js │ ├── mergesort.js │ ├── quicksort.js │ └── tree_interview_problems.js └── ruby │ ├── .DS_Store │ ├── .byebug_history │ ├── avl.rb │ ├── bfs.rb │ ├── bst.rb │ ├── dfs.rb │ ├── dijsktra │ ├── .DS_Store │ ├── dijkstra1.rb │ ├── dijkstra2.rb │ ├── graph.rb │ ├── heap2.rb │ └── priority_map.rb │ ├── dynamic_array.rb │ ├── fsa.rb │ ├── graph.rb │ ├── heap.rb │ ├── heap_sort.rb │ ├── lcs.rb │ ├── linked_list.rb │ ├── quick_sort.rb │ ├── radix_sort.rb │ ├── ring_buffer.rb │ ├── static_array.rb │ ├── topological_sort.rb │ └── trie.rb ├── doc ├── .DS_Store ├── algorithmic_design_interview.md ├── architectures.md ├── binary_heap.md ├── binary_search.md ├── binary_search_tree.md ├── bit_manipulation.md ├── breadth_first_search.md ├── depth_first_search.md ├── dijkstra.md ├── dynamic_array.md ├── dynamic_programming.md ├── finite_state_automata.md ├── graph.md ├── harvard-web-scalability-lecture.md ├── hash_map.md ├── http.md ├── java.md ├── linked_list.md ├── lru.md ├── manacher.md ├── merge_sort.md ├── object_oriented_design.md ├── quick_sort.md ├── radix.md ├── scalability.md ├── sql.md ├── stack_and_queue.md ├── stack_queue.md ├── suffix_tree.md ├── system_design_interview.md ├── topological_sort.md ├── tree_traversal.md └── trie.md ├── img ├── .DS_Store ├── archi.png ├── asyn_system.png ├── binary_tree.png ├── dijkstra.png ├── finite_state_automata.png ├── gfs.png ├── graph.png ├── heapify_up.png ├── heroku-postgre.png ├── load.png ├── partition.png ├── strongly_connected.png └── suffix_tree.png ├── run-node.js └── run-ruby.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.byebug_history 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Study Guide for Technical Interviews 2 | By now it should be week 14 for those who are reading this guide. This study 3 | guide outlines the major data structures and algorithms needed to succeed in 4 | technical interviews. The official guide was first written by Haseeb Qureshi, 5 | titled [How To Break Into The Tech Industry - a Guide to Job Hunting and Interviews][haseeb] 6 | on his personal blog site. This guide focuses on implementation details of the materials. However, please read Haseeb's guide at least once and follow his advice. 7 | 8 | [haseeb]: http://haseebq.com/#general-study 9 | 10 | ## Key Resources 11 | * Cracking the Coding Interview 6th Edition 12 | * Effective JavaScript 13 | * Coursera - Princeton Lectures on Algorithms (1 & 2) 14 | * Algorithm Design Manual 15 | 16 | ### Optional Resources (Podcast & Blog) 17 | * [Software Engineering Daily][daily] 18 | * [The Bike Shed][bikeshed] 19 | * [Hacker News][hacker] 20 | * [High Scalability][scale] 21 | 22 | [bikeshed]: http://bikeshed.fm/ 23 | [daily]: http://softwareengineeringdaily.com/ 24 | [scale]:http://highscalability.com/all-time-favorites/ 25 | [hacker]: https://news.ycombinator.com/ 26 | 27 | ## System Designs 28 | - [x] [Scalability Lecture & More][scalability] 29 | - [x] [Real Life Architectures][architectures] 30 | - [x] [Interview Tips][sys-design-interview] 31 | 32 | ## Data Structures 33 | - [x] [Stack & Queue][stack_and_queue] 34 | - [x] [LinkedList][linked_list] 35 | - [x] [HashMap][hash_map] 36 | - [x] [LRU Cache (Using LinkedList & HashMap)][lru] 37 | - [x] [Dynamic Array (Ring Buffer)][dynamic_array] 38 | - [x] [Binary Heap (Without decrease-key)][binary_heap] 39 | - [x] [Binary Search Tree][bst] 40 | - [x] Optional: AVL, 2-3 Tree, Red-Black Tree 41 | - [x] [Directed & Undirected Graphs][graph] 42 | - [x] [Tries][trie] 43 | - [x] [Suffix Tree][suffix] 44 | 45 | ## Algorithms 46 | 47 | ### Search 48 | - [x] [Binary Search][binary_search] 49 | - [x] [Breadth-first Search][bfs] 50 | - [x] [Depth-first Search][dfs] 51 | 52 | ### Sorting 53 | - [x] [Quick Sort (Partition Subroutine)][quick_sort] 54 | - [x] [Merge Sort][merge_sort] 55 | - [x] [Radix Sort][radix] 56 | - [x] Heap Sort 57 | 58 | ### Graphs 59 | - [x] [Tree Traversals (Pre-order, In-order, Post-order)][tree_traversal] 60 | - [x] [Objected-oriented Adjacency List][graph] 61 | - [x] [Adjacency Matrix][graph] 62 | - [x] [Topological Sort (Kahn's Algorithm)][topo] 63 | - [x] Topological Sort (Tarjan's Algorithm) 64 | - [x] [Dijkstra's Algorithm (Use PriorityQueue without decrease-key)][dijkstra] 65 | 66 | ### Dynamic Programming & Recursion 67 | - [x] [Longest Common Subsequence(Use matrices)][dynamic] 68 | - [x] [Fibonacci Sequence][dynamic] 69 | - [ ] Knapsack Problem (work in progress) 70 | 71 | ## Timeline 72 | Coursera might have taken down the algorithm lectures. Use this link 73 | https://www.youtube.com/user/Rabynovych/playlists 74 | 75 | ### Week 1 76 | #### Topics 77 | - [Dynamic Array or ArrayList(Java)][dynamic_array] 78 | - [Stack and Queue][stack_and_queue] 79 | - [StackQueue][stack_queue] 80 | - [LinkedList][linked_list] 81 | - [HashMap][hash_map] 82 | - [LRU Cache][lru] 83 | - [Binary Search][binary_search] 84 | - [Quick Sort][quick_sort] 85 | - [Merge Sort][merge_sort] 86 | - Coursera - Algorithms Part 1 87 | * Stacks & Queues 88 | * Elementary Sorts 89 | * Merge Sort 90 | * Quick Sort 91 | * Hash Functions & Linear Probing 92 | 93 | ### Week 2 94 | #### Topics 95 | - [Binary Heap][binary_heap] 96 | - Heap Sort 97 | - [Binary Search Tree][bst] 98 | - [Graph, Adjacency List, and Adjacency Matrix][graph] 99 | - [Breadth-first Search][bfs] 100 | - [Depth-first Search][dfs] 101 | - [Tree Traversals][tree_traversal] 102 | - [Topological Sort][topo] 103 | - Coursera - Algorithms Part 1 104 | * Binary Heaps 105 | * Heap Sort 106 | * Binary Search Tree 107 | * 2-3 Search Tree 108 | * Red-Black Tree 109 | 110 | ### Week 3 111 | #### Topics 112 | - [Dijkstra's Algorithm][dijkstra] 113 | - [Suffix Tree][suffix] 114 | - [Trie or Prefix Tree][trie] 115 | - [Radix][radix] 116 | - [Dynamic Programming][dynamic] 117 | - [Manacher's Algorithm][manacher] 118 | - [Finite State Automata][finite-state] 119 | - [Bit Manipulation][bit] 120 | - Coursera - Algorithms Part 2 121 | * Graphs (undirected) 122 | * Connected Components 123 | * Digraphs (directed) 124 | * Radix Sort 125 | * R-way Tries 126 | 127 | [scalability]: ./doc/scalability.md 128 | [architectures]: ./doc/architectures.md 129 | [sys-design-interview]: ./doc/system_design_interview.md 130 | [bfs]: ./doc/breadth_first_search.md 131 | [binary_heap]: ./doc/binary_heap.md 132 | [binary_search]: ./doc/binary_search.md 133 | [bit]: ./doc/bit_manipulation.md 134 | [bst]: ./doc/binary_search_tree.md 135 | [dfs]: ./doc/depth_first_search.md 136 | [dijkstra]: ./doc/dijkstra.md 137 | [dynamic_array]: ./doc/dynamic_array.md 138 | [dynamic]: ./doc/dynamic_programming.md 139 | [finite-state]: ./doc/finite_state_automata.md 140 | [graph]: ./doc/graph.md 141 | [hash_map]: ./doc/hash_map.md 142 | [linked_list]: ./doc/linked_list.md 143 | [lru]: ./doc/lru.md 144 | [manacher]: ./doc/manacher.md 145 | [merge_sort]: ./doc/merge_sort.md 146 | [quick_sort]: ./doc/quick_sort.md 147 | [radix]: ./doc/radix.md 148 | [stack_and_queue]: ./doc/stack_and_queue.md 149 | [stack_queue]: ./doc/stack_queue.md 150 | [tree_traversal]: ./doc/tree_traversal.md 151 | [trie]: ./doc/trie.md 152 | [topo]: ./doc/topological_sort.md 153 | [manacher]: ./doc/manacher.md 154 | [scale]: ./doc/scalability.md 155 | [suffix]: ./doc/suffix_tree.md 156 | -------------------------------------------------------------------------------- /code/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/code/.DS_Store -------------------------------------------------------------------------------- /code/javascript/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/code/javascript/.DS_Store -------------------------------------------------------------------------------- /code/javascript/binary-search-tree.js: -------------------------------------------------------------------------------- 1 | function Node(value) { 2 | this.value = value; 3 | this.left = null; 4 | this.right = null; 5 | } 6 | 7 | function BSTree() { 8 | this.root = undefined; 9 | this.nodeCount = 0; 10 | } 11 | 12 | BSTree.prototype.insert = function(value) { 13 | if (this.root) { 14 | BSTree.insert(this.root, value); 15 | this.nodeCount += 1; 16 | } else { 17 | this.root = new Node(value); 18 | } 19 | }; 20 | 21 | BSTree.insert = function(node, value) { 22 | if (node) { 23 | if (value <= node.value) { 24 | node.left = BSTree.insert(node.left, value); 25 | } else { 26 | node.right = BSTree.insert(node.right, value); 27 | } 28 | return node; 29 | } else { 30 | return new Node(value); 31 | } 32 | }; 33 | 34 | BSTree.prototype.find = function(value) { 35 | return BSTree.find(this.root, value); 36 | }; 37 | 38 | //Class method is the way to go, just because we want to do a recursive 39 | //version of the find method. If we do iterative, we can start with the 40 | //instance method 41 | BSTree.find = function(node, value) { 42 | if (node) { 43 | if (value < node.value) { 44 | return BSTree.find(node.left, value); 45 | } else if (node.value < value) { 46 | return BSTree.find(node.right, value); 47 | } else { 48 | return node; 49 | } 50 | } else { 51 | return null; 52 | } 53 | }; 54 | 55 | BSTree.prototype.max = function() { 56 | return BSTree.max(this.root).value; 57 | }; 58 | 59 | BSTree.prototype.min = function() { 60 | return BSTree.min(this.root).value; 61 | }; 62 | 63 | BSTree.prototype.height = function() { 64 | return BSTree.height(this.root); 65 | }; 66 | 67 | BSTree.prototype.inorder = function() { 68 | return BSTree.inorder(this.root); 69 | }; 70 | 71 | BSTree.prototype.delete = function(value) { 72 | BSTree.delete(this.root, value); 73 | }; 74 | 75 | BSTree.max = function(node) { 76 | // The right most node is the max 77 | if (node) { 78 | if (node.right) { 79 | return BSTree.max(node.right); 80 | } else { 81 | return node; 82 | } 83 | } else { 84 | return null; 85 | } 86 | }; 87 | 88 | BSTree.min = function(node) { 89 | // The left most node is the min 90 | if (node) { 91 | if (node.left) { 92 | return BSTree.min(node.left); 93 | } else { 94 | return node; 95 | } 96 | } else { 97 | return null; 98 | } 99 | }; 100 | 101 | BSTree.height = function(node) { 102 | if (node) { 103 | if (!node.left && !node.right) { 104 | return 0; 105 | } else { 106 | var depth = []; 107 | if (node.left) { 108 | depth += [1 + BSTree.height(node.left)]; 109 | } 110 | if (node.right) { 111 | depth += [1 + BSTree.height(node.right)]; 112 | } 113 | return Math.max(...depth); 114 | } 115 | } else { 116 | return -1; 117 | } 118 | }; 119 | 120 | BSTree.inorder = function(node) { 121 | if (node) { 122 | var arr = []; 123 | arr += BSTree.inorder(node.left); 124 | arr += [node.value]; 125 | arr += BSTree.inorder(node.right); 126 | return arr; 127 | } else { 128 | return []; 129 | } 130 | }; 131 | 132 | BSTree.deleteMin = function(node) { 133 | if (node) { 134 | if (node.left) { 135 | node.left = BSTree.deleteMin(node.left); 136 | return node; 137 | } else { 138 | return node.right; 139 | } 140 | } else { 141 | return null; 142 | } 143 | }; 144 | 145 | BSTree.delete = function(node, value) { 146 | if (node) { 147 | if (value < node.value) { 148 | node.left = BSTree.delete(node.left, value); 149 | } else if (node.value < value) { 150 | node.right = BSTree.delete(node.right, value); 151 | } else { 152 | if (!node.right) { 153 | return node.left; 154 | } 155 | if (!node.left) { 156 | return node.right; 157 | } 158 | //Once the node is found, replace it with the minimum from its right child 159 | var target = node; 160 | node = target.right.min(); 161 | node.right = BSTree.deleteMin(node.right); 162 | node.left = target.left; 163 | } 164 | } else { 165 | return null; 166 | } 167 | }; 168 | 169 | // var tree = new BSTree(); 170 | // while (tree.nodeCount < 10) { 171 | // tree.insert(Math.floor(Math.random()*10)); 172 | // } 173 | // console.log(tree.height()); 174 | module.exports = BSTree; 175 | -------------------------------------------------------------------------------- /code/javascript/ctci.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is a repository for hard coding interview problems from Cracking 3 | the Coding Interview 6th Edition, Chapter 17. 4 | 5 | The following code is written in JavaScript ES6 6 | */ 7 | 'use strict'; 8 | //====================================================================== 9 | /* 10 | Adding Without Plus: write a function that adds two numbers. 11 | You should not use add or any arithmetic operators 12 | */ 13 | function bitAddNaive(num1, num2) { 14 | let binary1 = num1.toString(2); 15 | let binary2 = num2.toString(2); 16 | let length; 17 | if (binary1.length > binary2.length) { 18 | length = binary1.length; 19 | while (binary2.length < length) { 20 | binary2 = 0 + binary2; 21 | } 22 | } else { 23 | length = binary2.length; 24 | while (binary1.length < length) { 25 | binary1 = 0 + binary1; 26 | } 27 | } 28 | let sum = ""; 29 | let carryOver = 0; 30 | for(let i = length - 1; i >= 0; i--) { 31 | if (binary1[i] === '1' && binary2[i] === '1') { 32 | if (carryOver > 0) { 33 | sum = '1' + sum; 34 | carryOver = 1; 35 | } else { 36 | sum = '0' + sum; 37 | carryOver = 1; 38 | } 39 | } else if (binary1[i] === '1' || binary2[i] === '1') { 40 | if (carryOver > 0) { 41 | sum = '0' + sum; 42 | carryOver = 1; 43 | } else { 44 | sum = '1' + sum; 45 | carryOver = 0; 46 | } 47 | } else { 48 | if (carryOver > 0) { 49 | sum = '1' + sum; 50 | carryOver = 0; 51 | } else { 52 | sum = '0' + sum; 53 | carryOver = 0; 54 | } 55 | } 56 | } 57 | if (carryOver > 0) { 58 | sum = '1' + sum; 59 | } 60 | return parseInt(sum, 2); 61 | } 62 | 63 | function bitAdd(sum, carry) { 64 | if (carry === 0) { 65 | console.log(`sum: ${sum}, carry: ${carry}`); 66 | return sum; 67 | } else { 68 | console.log(`sum: ${sum}, carry: ${carry}`); 69 | let sumBit = sum ^ carry; 70 | let carryBit = (sum & carry) << 1 ; 71 | return bitAdd(sumBit, carryBit); 72 | } 73 | } 74 | 75 | /* 76 | Shuffle: Write a method to shuffle a deck of card. It must be a perfect 77 | shuffle, in other words, each of the 52! permutation has to be equally 78 | likely. Assume that you are given a random number generator which is perfect. 79 | */ 80 | 81 | function shuffle(deck) { 82 | let deckSize = deck.length; 83 | let shuffled = []; 84 | while (shuffled.length < deckSize) { 85 | let randomIndex = Math.floor(Math.random()*deck.length); 86 | shuffled.push(deck[randomIndex]); 87 | deck.splice(randomIndex, 1); 88 | } 89 | return shuffled; 90 | } 91 | 92 | //This is the solution from CTCI 93 | function anotherShuffle(deck) { 94 | for (let i = 0; i < deck.length ; i++) { 95 | let randomIndex = Math.floor(Math.random()*i); 96 | let temp = deck[randomIndex]; 97 | deck[randomIndex] = deck[i]; 98 | deck[i] = temp; 99 | } 100 | return deck; 101 | } 102 | 103 | // Assign each card an id, ranging from 1 to 52; 104 | let deck = []; 105 | for (let i = 1; i <= 52; i++) { 106 | deck.push(i); 107 | } 108 | 109 | /* 110 | Random Set: Write a method to randomly generate a set of m integers 111 | from an array of size n. Each element must have equal probability of being 112 | chosen. 113 | */ 114 | 115 | 116 | /* 117 | Missing Number: an array A constains all the integers from 0 to n, 118 | except for one number which is missing. 119 | 120 | In this problem, we cannot access an entire integer in A with a single 121 | operation. The elements of A are represented in binary, and the only 122 | operation we can use to access them is "fetch the jth bit of A[i]," 123 | which takes constaint time. 124 | 125 | Write code to find the missing integer. Can do you it in O(n) time? 126 | */ 127 | -------------------------------------------------------------------------------- /code/javascript/graphs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | //Graphs 3 | class Vertex { 4 | constructor(value) { 5 | this.value = value; 6 | this.inEdges = []; 7 | this.outEdges = []; 8 | this.visited = false; 9 | } 10 | 11 | visit() { 12 | this.visited = true; 13 | } 14 | 15 | deleteOutEdge(outEdge) { 16 | for (let i = 0; i < this.outEdges.length; i++) { 17 | if (this.outEdges[i] === outEdge) { 18 | this.outEdges.splice(i, 1); 19 | break; 20 | } 21 | } 22 | } 23 | 24 | deleteInEdge(inEdge) { 25 | for (let i = 0; i < this.inEdges.length; i++) { 26 | if (this.inEdges[i] === inEdge) { 27 | this.inEdges.splice(i, 1); 28 | break; 29 | } 30 | } 31 | } 32 | } 33 | 34 | class Edge { 35 | constructor(fromVertex, toVertex, cost) { 36 | this.fromVertex = fromVertex; 37 | this.toVertex = toVertex; 38 | this.cost = cost || 1; 39 | fromVertex.outEdges.push(this); 40 | toVertex.inEdges.push(this); 41 | } 42 | 43 | destroy() { 44 | this.toVertex.deleteInEdge(this); 45 | this.fromVertex.deleteOutEdge(this); 46 | this.toVertex = undefined; 47 | this.fromVertex = undefined; 48 | } 49 | } 50 | 51 | function breadthFirstSearch(vertex, target) { 52 | let queue = [vertex]; 53 | while (queue.length > 0) { 54 | let probeVertex = queue.shift(); 55 | probeVertex.visit(); 56 | if (probeVertex.value === target) { 57 | return probeVertex; 58 | } else { 59 | probeVertex.outEdges.forEach((outEdge) => { 60 | let neighborVertex = outEdge.toVertex; 61 | if (!neighborVertex.visit()) { 62 | queue.push(neighborVertex); 63 | } 64 | }); 65 | } 66 | } 67 | return null; 68 | } 69 | 70 | function depthFirstSearch(vertex, target) { 71 | if (vertex.value === target) { 72 | return vertex; 73 | } else { 74 | for (let i = 0; i < vertex.outEdges.length; i++) { 75 | let neighborVertex = vertex.outEdges[i].toVertex; 76 | let searchResult = depthFirstSearch(neighborVertex, target); 77 | if (searchResult) { 78 | return searchResult; 79 | } 80 | } 81 | } 82 | return null; 83 | } 84 | 85 | const nodeA = new Vertex(1); 86 | const nodeB = new Vertex(2); 87 | const nodeC = new Vertex(3); 88 | const nodeD = new Vertex(4); 89 | const AtoB = new Edge(nodeA, nodeB); 90 | const AtoC = new Edge(nodeA, nodeC); 91 | const AtoD = new Edge(nodeA, nodeD); 92 | -------------------------------------------------------------------------------- /code/javascript/heap.js: -------------------------------------------------------------------------------- 1 | function BinaryMaxHeap() { 2 | this.store = []; 3 | } 4 | 5 | BinaryMaxHeap.prototype.count = function() { 6 | return this.store.length; 7 | }; 8 | 9 | BinaryMaxHeap.prototype.extract = function() { 10 | var tempHolder = this.store[0]; 11 | this.store[0] = this.store[this.count() - 1]; 12 | this.store[this.count() - 1] = tempHolder; 13 | var returnVal = this.store.pop(); 14 | BinaryMaxHeap.heapifyDown(this.store, 0); 15 | return returnVal; 16 | }; 17 | 18 | BinaryMaxHeap.prototype.peek = function() { 19 | return this.store[0]; 20 | }; 21 | 22 | BinaryMaxHeap.prototype.push = function(val) { 23 | this.store.push(val); 24 | BinaryMaxHeap.heapifyUp(this.store, this.count() - 1); 25 | return val; 26 | }; 27 | 28 | BinaryMaxHeap.childIndices = function(length, parentIdx) { 29 | var childIndices = []; 30 | var firstChild, secChild; 31 | firstChild = parentIdx*2 + 1; 32 | secChild = parentIdx*2 + 2; 33 | 34 | if (firstChild < length) { 35 | childIndices.push(firstChild); 36 | } 37 | if (secChild < length) { 38 | childIndices.push(secChild); 39 | } 40 | return childIndices; 41 | }; 42 | 43 | BinaryMaxHeap.parentIndex = function(childIdx) { 44 | if (childIdx === 0) { 45 | return null; 46 | } else { 47 | return Math.floor((childIdx - 1)/2); 48 | } 49 | }; 50 | 51 | BinaryMaxHeap.heapifyUp = function(array, childIdx) { 52 | var parentIdx, temp; 53 | parentIdx = BinaryMaxHeap.parentIndex(childIdx); 54 | 55 | if (childIdx && childIdx > 0) { 56 | if (array[parentIdx] < array[childIdx]) { 57 | temp = array[parentIdx]; 58 | array[parentIdx] = array[childIdx]; 59 | array[childIdx] = temp; 60 | } 61 | //traversing upward 62 | BinaryMaxHeap.heapifyUp(array, parentIdx); 63 | } 64 | }; 65 | 66 | BinaryMaxHeap.heapifyDown = function(array, parentIdx) { 67 | var childIdxes, temp; 68 | childIdxes = BinaryMaxHeap.childIndices(array.length, parentIdx); 69 | 70 | if (childIdxes.length === 2) { 71 | if (array[childIdxes[0]] > array[childIdxes[1]] && array[childIdxes[0]] > array[parentIdx]) { 72 | temp = array[parentIdx]; 73 | array[parentIdx] = array[childIdxes[0]]; 74 | array[childIdxes[0]] = temp; 75 | BinaryMaxHeap.heapifyDown(array, childIdxes[0]); 76 | } else if (array[childIdxes[1]] > array[parentIdx]){ 77 | temp = array[parentIdx]; 78 | array[parentIdx] = array[childIdxes[1]]; 79 | array[childIdxes[1]] = temp; 80 | BinaryMaxHeap.heapifyDown(array, childIdxes[1]); 81 | } 82 | } else if (childIdxes.length === 1) { 83 | if (array[childIdxes[0]] > array[parentIdx]) { 84 | temp = array[parentIdx]; 85 | array[parentIdx] = array[childIdxes[0]]; 86 | array[childIdxes[0]] = temp; 87 | BinaryMaxHeap.heapifyDown(array, childIdxes[0]); 88 | } 89 | } 90 | }; 91 | 92 | var sampleSize = 10000000; 93 | 94 | var startTime = new Date(); 95 | var heap = new BinaryMaxHeap(); 96 | for (var i = 0; i < sampleSize; i ++) { 97 | heap.push(Math.floor(Math.random()*sampleSize)); 98 | } 99 | 100 | var result = []; 101 | for (i = 0; i < sampleSize; i ++) { 102 | result.push(heap.extract()); 103 | } 104 | var endTime = new Date(); 105 | // console.log(heap); 106 | // console.log(result); 107 | console.log(endTime.getTime() - startTime.getTime()); 108 | -------------------------------------------------------------------------------- /code/javascript/linked-list.js: -------------------------------------------------------------------------------- 1 | function Link(key, val) { 2 | this.key = key; 3 | this.val = val; 4 | this.next = undefined; 5 | this.prev = undefined; 6 | } 7 | 8 | // JSON is backed by hashMap so... I probably shouldn't cheat 9 | Link.prototype.toString = function(){ 10 | return this.key + ": " + this.val; 11 | }; 12 | 13 | function LinkedList() { 14 | this.head = new Link(); 15 | this.tail = new Link(); 16 | this.head.next = this.tail; 17 | this.tail.prev = this.head; 18 | } 19 | 20 | LinkedList.prototype.isEmpty = function() { 21 | return this.head.next === this.tail; 22 | }; 23 | 24 | LinkedList.prototype.first = function() { 25 | if (this.isEmpty()) { 26 | return null; 27 | } else { 28 | return this.head.next; 29 | } 30 | }; 31 | 32 | LinkedList.prototype.last = function() { 33 | if (this.isEmpty()) { 34 | return null; 35 | } else { 36 | return this.tail.prev; 37 | } 38 | }; 39 | 40 | LinkedList.prototype.insert = function(key, val) { 41 | var newLink = new Link(key, val); 42 | var currLast = this.tail.prev; 43 | // Connect them 44 | currLast.next = newLink; 45 | newLink.prev = currLast; 46 | newLink.next = this.tail; 47 | this.tail.prev = newLink; 48 | return newLink; 49 | }; 50 | 51 | LinkedList.prototype.get = function(key) { 52 | var val; 53 | this.each(function(link) { 54 | if (link.key === key) { 55 | val = link.val; 56 | } 57 | }); 58 | return val; 59 | }; 60 | 61 | LinkedList.prototype.remove = function(key) { 62 | var returnVal; 63 | this.each(function(link) { 64 | if (link.key === key) { 65 | link.prev.next = link.next; 66 | link.next.prev = link.prev; 67 | link.next = undefined; 68 | link.prev = undefined; 69 | returnVal = link; 70 | } 71 | }); 72 | return returnVal; 73 | }; 74 | 75 | LinkedList.prototype.each = function(cb) { 76 | var currLink = this.head.next; 77 | while (currLink !== this.tail) { 78 | cb(currLink); 79 | if (currLink.next === undefined) { 80 | // In case of removal 81 | return null; 82 | } 83 | currLink = currLink.next; 84 | } 85 | return null; 86 | }; 87 | 88 | LinkedList.prototype.isInclude = function(key) { 89 | var boolean = false; 90 | this.each(function(link) { 91 | if (link.key === key) { 92 | boolean = true; 93 | } 94 | }); 95 | return boolean; 96 | }; 97 | 98 | module.exports = LinkedList; 99 | -------------------------------------------------------------------------------- /code/javascript/lru.js: -------------------------------------------------------------------------------- 1 | var LinkedList = require('./linked-list.js'); 2 | 3 | function LRUCache(max, cb) { 4 | this.map = {}; 5 | this.store = new LinkedList(); 6 | this.MAX = max; 7 | //imagine this callback is computationally heavy 8 | //if the result already exists, don't compute 9 | this.cb = cb; 10 | } 11 | 12 | LRUCache.prototype.count = function() { 13 | return Object.keys(this.map).length; 14 | }; 15 | 16 | LRUCache.prototype.get = function(key) { 17 | if (this.map[key]) { 18 | var link = this.map[key]; 19 | this.refreshLink(link); 20 | return link.val; 21 | } else { 22 | var val = this.calc(key); 23 | return val; 24 | } 25 | }; 26 | 27 | LRUCache.prototype.calc = function(key) { 28 | var val = this.cb(key); 29 | var newLink = this.store.insert(key, val); 30 | this.map[key] = newLink; 31 | 32 | if (this.count() > this.MAX) { 33 | this.eject(); 34 | } 35 | return val; 36 | }; 37 | 38 | LRUCache.prototype.refreshLink = function(link) { 39 | //remove link from list 40 | link.prev.next = link.next; 41 | link.next.prev = link.prev; 42 | 43 | //add link to last 44 | link.prev = this.store.last; 45 | link.next = this.store.last.next; 46 | this.store.last.next = link; 47 | }; 48 | 49 | LRUCache.prototype.eject = function() { 50 | //remove first link 51 | var rmLink = this.store.first; 52 | rmLink.prev.next = rmLink.next; 53 | rmLink.next.prev = rmLink.prev; 54 | this.map[rmLink.key] = undefined; 55 | return null; 56 | }; 57 | 58 | //======================= TEST ========================================= 59 | var cache = new LRUCache(5, function(key){ 60 | var i = 0; 61 | // BLOCK THE DOOOOOOOMMMMMMM! 62 | while (i < 1000000000) { 63 | i += 1; 64 | } 65 | return key; 66 | }); 67 | 68 | console.log(cache.get(1)); 69 | console.log(cache.get(2)); 70 | console.log(cache.get(1)); 71 | console.log(cache.get(3)); 72 | -------------------------------------------------------------------------------- /code/javascript/mergesort.js: -------------------------------------------------------------------------------- 1 | function mergeSort(array) { 2 | if (array.length < 2) { 3 | return array; 4 | } else { 5 | let midIndex = Math.floor(array.length/2); 6 | let left = array.slice(0, midIndex); 7 | let right = array.slice(midIndex); 8 | return merge(mergeSort(left), mergeSort(right)); 9 | } 10 | } 11 | 12 | function merge(left, right) { 13 | let merged = []; 14 | while (left.length > 0 && right.length > 0) { 15 | if (left[0] <= right[0]) { 16 | merged.push(left.shift()); 17 | } else { 18 | merged.push(right.shift()); 19 | } 20 | } 21 | return merged.concat(left, right); 22 | } 23 | -------------------------------------------------------------------------------- /code/javascript/quicksort.js: -------------------------------------------------------------------------------- 1 | function inPlaceQuickSort(array, startIndex, arrayLength) { 2 | if (arrayLength >= 2) { 3 | let pivotIndex = partition(array, startIndex, arrayLength); 4 | let leftLength = pivotIndex - startIndex; 5 | let rightLength = arrayLength - leftLength - 1; 6 | inPlaceQuickSort(array, startIndex, leftLength); 7 | inPlaceQuickSort(array, pivotIndex + 1, rightLength); 8 | } 9 | } 10 | 11 | function partition(array, startIndex, subArrayLength) { 12 | let pivotIndex = startIndex; 13 | let pivot = array[startIndex]; 14 | for (let i = startIndex + 1; i < startIndex + subArrayLength; i++) { 15 | let val = array[i]; 16 | if (val < pivot) { 17 | array[i] = array[pivotIndex + 1]; 18 | array[pivotIndex + 1] = pivot; 19 | array[pivotIndex] = val; 20 | pivotIndex += 1; 21 | } 22 | } 23 | return pivotIndex; 24 | } 25 | -------------------------------------------------------------------------------- /code/javascript/tree_interview_problems.js: -------------------------------------------------------------------------------- 1 | var BSTree = require('./binary-search-tree.js'); 2 | 3 | function Node(value) { 4 | this.value = value; 5 | this.left = null; 6 | this.right = null; 7 | } 8 | 9 | /* 10 | This is not optimized but it's most intuitive. It runs in O(nlog(n)) time 11 | 12 | Question: How to get height of a binary search tree? Or any binary tree 13 | in general? 14 | Use recursion and DFS style 15 | Base case: If a node doesn't have any children, it's a leaf node, return 0 16 | Base case: If a node is null, return -1 17 | Inductive step: The height of a tree is the height of its left or right subtree 18 | plus one. But make sure to pick out the taller subtree. 19 | */ 20 | function getHeight(node) { 21 | if (node === null) { 22 | return -1; 23 | } else { 24 | return Math.max(getHeight(node.left), getHeight(node.right)) + 1; 25 | } 26 | } 27 | 28 | function isBalanced(root) { 29 | if (root === null) { 30 | return true; 31 | } else { 32 | var heightDiff = getHeight(root.left) - getHeight(root.right); 33 | if (Math.abs(heightDiff) > 1) { 34 | return false; 35 | } else { 36 | return isBalanced(root.left) && isBalanced(root.right); 37 | } 38 | } 39 | } 40 | 41 | /* 42 | This is the optimized version. It should run in O(n) time with O(h) space 43 | */ 44 | function checkHeight(root) { 45 | if (root === null) { 46 | return -1; 47 | } else { 48 | var leftHeight = checkHeight(root.left); 49 | if (leftHeight === false) { 50 | return false; 51 | } 52 | 53 | var rightHeight = checkHeight(root.right); 54 | if (rightHeight === false) { 55 | return false; 56 | } 57 | 58 | var heightDiff = Math.abs(leftHeight - rightHeight); 59 | if (heightDiff > 1) { 60 | return false; 61 | } else { 62 | return Math.max(leftHeight, rightHeight) + 1; 63 | } 64 | } 65 | } 66 | 67 | function checkBalance(root) { 68 | return checkHeight(root) !== false; 69 | } 70 | 71 | function validateBST(root) { 72 | if (root.left && root.right) { 73 | return (BSTree.max(root.left).value <= root.value && 74 | BSTree.max(root.right).value > root.value) && 75 | (validateBST(root.left) && validateBST(root.right)); 76 | } else if (root.left) { 77 | return (BSTree.max(root.left).value <= root.value) && 78 | validateBST(root.left); 79 | } else if (root.right) { 80 | return (BSTree.max(root.right).value > root.value) && 81 | validateBST(root.right); 82 | } else { 83 | return true; 84 | } 85 | } 86 | var leftThree = new Node(10); 87 | var leftTwo = new Node(3); 88 | var leftOne = new Node(5); 89 | var rightTwo = new Node(6); 90 | var randomRoot = new Node(7); 91 | var rightOne = new Node(8); 92 | randomRoot.left = leftOne; 93 | randomRoot.right = rightOne; 94 | leftOne.left = leftTwo; 95 | leftOne.right = rightTwo; 96 | leftTwo.left = leftThree; 97 | console.log(validateBST(randomRoot)); 98 | 99 | 100 | var sampleTree = new BSTree(); 101 | sampleTree.insert(5); 102 | sampleTree.insert(2); 103 | sampleTree.insert(1); 104 | sampleTree.insert(3); 105 | sampleTree.insert(7); 106 | sampleTree.insert(6); 107 | sampleTree.insert(8); 108 | sampleTree.insert(9); 109 | sampleTree.insert(10); 110 | console.log(sampleTree.inorder()); 111 | console.log(isBalanced(sampleTree.root)); 112 | console.log(checkBalance(sampleTree.root)); 113 | console.log(validateBST(sampleTree.root)); 114 | -------------------------------------------------------------------------------- /code/ruby/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/code/ruby/.DS_Store -------------------------------------------------------------------------------- /code/ruby/.byebug_history: -------------------------------------------------------------------------------- 1 | exit 2 | linkedlist_arr[3].to_s 3 | linkedlist_arr[2].to_s 4 | linkedlist_arr[1].to_s 5 | linkedlist_arr[0].to_s 6 | linkedlist_arr[0] 7 | linkedlist_arr[1].first.next.val 8 | linkedlist_arr[0].first.next.val 9 | linkedlist_arr[0].first.val 10 | linkedlist_arr[0].first 11 | linkedlist_arr[0] 12 | linkedlist_arr 13 | c 14 | exit 15 | parent.val.left.value 16 | parent.val.left 17 | parent.val 18 | parent 19 | exit 20 | eixt 21 | linked_lists_arr[2] 22 | linked_lists_arr[1] 23 | linked_lists_arr 24 | exit 25 | linked_lists_arr[1] 26 | linked_lists_arr[4] 27 | linked_lists_arr[3] 28 | linked_lists_arr[2] 29 | linked_lists_arr[1] 30 | linked_lists_arr[0] 31 | linked_lists_arr.first 32 | c 33 | linked_lists_arr[0].first.next.next.val 34 | linked_lists_arr[0].first.next.val 35 | linked_lists_arr[0].first.val 36 | linked_lists_arr[0].first 37 | exit 38 | c 39 | 2**(n+1) 40 | counter 41 | 2**(n-1) 42 | exit 43 | counter 44 | linked_lists_arr.length 45 | linked_Lists_arr.length 46 | exit 47 | (n-1) 48 | n 49 | 2**(n-1) 50 | 2 ** (n-1) < counter 51 | counter 52 | c 53 | 2 ** (n-1) < counter 54 | 2 ** (n-1) 55 | counter 56 | exit 57 | counter 58 | linked_lists_arr[7].first.val 59 | linked_lists_arr[6].first.val 60 | linked_lists_arr[5].first.val 61 | linked_lists_arr[4].first.val 62 | linked_lists_arr[3].first.val 63 | linked_lists_arr[2].first.next.val 64 | linked_lists_arr[2].first.val 65 | linked_lists_arr[1].first.val 66 | linked_lists_arr[1].first.next.val 67 | linked_lists_arr[1].first.next.next.val 68 | linked_lists_arr[1].first.next.val 69 | linked_lists_arr[1].first.val 70 | linked_lists_arr[1].first.next.val 71 | linked_lists_arr[1].first.next.value 72 | linked_lists_arr[1].first.next 73 | linked_lists_arr[1].first 74 | linked_lists_arr[1] 75 | linked_lists_arr[0] 76 | linked_lists.arr[0] 77 | exit 78 | linked_lists_arr[0].last 79 | linked_lists_arr[0].first 80 | linked_lists_arr[0].head 81 | linked_lists_arr[0] 82 | exit 83 | current_node.right 84 | current_node.left 85 | current_node 86 | c 87 | node 88 | key 89 | c 90 | node 91 | key 92 | exit 93 | count 94 | exit 95 | c 96 | idx 97 | count 98 | n 99 | count 100 | n 101 | count 102 | n 103 | count 104 | n 105 | c 106 | count 107 | c 108 | count 109 | c 110 | count 111 | ccount 112 | c 113 | count[idx + 1] 114 | count[idx + 1] += count[idx] 115 | count[idx + 1] 116 | count[idx] 117 | 69.chr 118 | 67.chr 119 | 20.chr 120 | 95.chr 121 | 243.chr 122 | 2.chr 123 | 1.chr 124 | idx.chr 125 | idx 126 | exit 127 | count 128 | exit 129 | count 130 | exit 131 | count 132 | exit 133 | count[str_arr[i][d].ord + 1] 134 | str_arr[i][d].ord + 1 135 | str_arr[i][d].ord 136 | str_arr[i][d] 137 | i 138 | -------------------------------------------------------------------------------- /code/ruby/avl.rb: -------------------------------------------------------------------------------- 1 | class AVLTreeNode 2 | attr_accessor :link, :value, :balance 3 | def initialize(value) 4 | @value = value 5 | @link = [nil, nil] 6 | @balance = 0 7 | end 8 | end 9 | 10 | class AVLTree 11 | def initialize 12 | @root = nil 13 | end 14 | 15 | def insert(value) 16 | @root = AVLTree.insert!(@root, value) 17 | end 18 | 19 | # The reason for this implementation is to make condense the two symmetric cases 20 | # into one to minimize potential bugs 21 | def self.insert!(node, value) 22 | return AVLTreeNode.new(value) unless node 23 | # 0 and 1 are left and right 24 | dir = value < node.value? 0 : 1 25 | node.link[dir] = AVLTree.insert!(node.link[dir], value) 26 | node 27 | end 28 | 29 | # Generic BST can degenerate into linear data structures in 3 cases 30 | # 1. data is inserted in ascending order 31 | # 2. data is inserted in descending order 32 | # 3. data is inserted in alternating order 33 | 34 | # A BST meets the AVL invariant is balanced if the height of a node's 35 | # subtrees differ by no more than 1 36 | 37 | # To main BST + AVL invariant, one must do rotation 38 | 39 | def self.single_rotation!(root, dir) 40 | other_dir = dir == 0 ? 1 : 0 41 | save = root.link[other_dir] 42 | root.link[other_dir] = save.link[dir] 43 | save.link[dir] = root 44 | save 45 | end 46 | 47 | # The second case that would violate AVL invariant is if a subtree is too 48 | # long in the opposite direction, where two rotations are required to 49 | # return the structure to balance 50 | 51 | def self.double_rotation!(root, dir) 52 | other_dir = dir == 0 ? 1 : 0 53 | save = root.link[other_dir].link[dir] 54 | root.link[other_dir].link[dir] = save.link[other_dir] 55 | save.link[other_dir] = root.link[other_dir] 56 | root.link[other_dir] = save 57 | 58 | save = root.link[other_dir] 59 | root.link[other_dir] = save.link[dir] 60 | save.link[dir] = root 61 | 62 | save 63 | end 64 | # Balance factor = height(right_sub) - height(left_sub) 65 | def self.adjust_balance!(root, dir, bal) 66 | other_dir = dir == 0 ? 1 : 0 67 | 68 | n = root.link[dir] 69 | nn = n.link[other_dir] 70 | 71 | if nn.balance == 0 72 | root.balance = 0 73 | n.balance = 0 74 | elsif nn.balance == bal 75 | root.balance = -bal 76 | n.balance = 0 77 | else 78 | root.balance = 0 79 | n.balance = bal 80 | end 81 | 82 | nn.balance = 0 83 | end 84 | 85 | def self.insert_balance!(root, dir) 86 | other_dir = dir == 0 ? 1 : 0 87 | 88 | n = root.link[dir] 89 | bal = dir == ? -1 : +1 90 | 91 | if n.balance == bal 92 | root.balance = 0 93 | n.balance = 0 94 | root = AVLTree.single_rotation!(root, other_dir) 95 | else 96 | AVLTree.adjust_balance!(root, dir, bal) 97 | root = AVLTree.double_rotation!(root, other_dir) 98 | end 99 | root 100 | end 101 | 102 | def self.insert!(node, value, done) 103 | return [AVLTreeNode.new(value), false] unless node 104 | dir = value < node.value ? 0 : 1 105 | node.link[dir], done = AVLTree.insert!(node.link[dir], value, done) 106 | 107 | unless done 108 | node.balance += (dir == 0 ? -1 : 1) 109 | 110 | if node.balance == 0 111 | done = true 112 | elsif node.balance.abs > 1 113 | node = AVLTree.insert_balance!(node, dir) 114 | done = true 115 | end 116 | end 117 | [node, done] 118 | end 119 | 120 | def insert(value) 121 | done = false 122 | @root, done = AVLTree.insert!(@root, value, done) 123 | true 124 | end 125 | 126 | def self.remove_balance!(root, dir, done) 127 | other_dir = dir == 0 ? 1 : 0 128 | 129 | n = root.link[other_dir] 130 | bal = dir == 0 ? -1 : 1 131 | 132 | if n.balance == -bal 133 | root.balance = 0 134 | n.balance = 0 135 | root = AVLTree.single_rotation!(root, dir) 136 | elsif n.balance == bal 137 | AVLTree.adjust_balance!(root, other_dir, -bal) 138 | root = AVLTree.double_rotation!(root, dir) 139 | else 140 | root.balance = -bal 141 | n.balance = bal 142 | root = AVLTree.single_rotation!(root, dir) 143 | done = true 144 | end 145 | 146 | [root, done] 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /code/ruby/bfs.rb: -------------------------------------------------------------------------------- 1 | class Vertex 2 | attr_accessor :value, :in_edges, :out_edges 3 | def initialize(value) 4 | @value, @in_edges, @out_edges = value, [], [] 5 | @visited = false 6 | end 7 | 8 | def visited? 9 | @visited 10 | end 11 | 12 | def visit! 13 | @visited = true 14 | end 15 | end 16 | 17 | class Edge 18 | attr_accessor :to_vertex, :from_vertex, :cost 19 | def initialize(from_vertex, to_vertex, cost = 1) 20 | self.from_vertex = from_vertex 21 | self.to_vertex = to_vertex 22 | self.cost = cost 23 | 24 | to_vertex.in_edges << self 25 | from_vertex.out_edges << self 26 | end 27 | 28 | def destroy! 29 | self.to_vertex.in_edges.delete(self) 30 | self.to_vertex = nil 31 | self.from_vertex.out_edges.delete(self) 32 | self.from_vertex = nil 33 | end 34 | end 35 | 36 | def bfs(node, target) 37 | queue = [node] 38 | until queue.empty? 39 | probe = queue.shift 40 | probe.visit! 41 | return probe if probe.value == target 42 | probe.out_edges.each do |edge| 43 | queue << edge.to_vertex unless edge.to_vertex.visited? 44 | end 45 | end 46 | nil 47 | end 48 | 49 | v1 = Vertex.new("Calvin") 50 | v2 = Vertex.new("Steven") 51 | v3 = Vertex.new("Matt") 52 | v4 = Vertex.new("Loki") 53 | e1 = Edge.new(v1, v2) 54 | e6 = Edge.new(v1, v3) 55 | e2 = Edge.new(v2, v4) 56 | e4 = Edge.new(v4, v1) 57 | e5 = Edge.new(v4, v2) 58 | 59 | result = bfs(v1, "Loki") 60 | p result.value 61 | p v1.visited? 62 | p v2.visited? 63 | p v3.visited? 64 | p v4.visited? 65 | # def bfs(root, target) 66 | # queue = [root] 67 | # until queue.empty? 68 | # probe = queue.shift 69 | # return probe if probe.value == target 70 | # probe.children.each do |child| 71 | # queue << child 72 | # end 73 | # end 74 | # nil 75 | # end 76 | -------------------------------------------------------------------------------- /code/ruby/bst.rb: -------------------------------------------------------------------------------- 1 | require 'byebug' 2 | 3 | class BSTNode 4 | attr_accessor :left, :right 5 | attr_reader :value 6 | 7 | def initialize(value) 8 | @value = value 9 | @left = nil 10 | @right = nil 11 | end 12 | end 13 | 14 | class BinarySearchTree 15 | def initialize 16 | @root_node = nil 17 | end 18 | 19 | def insert(value) 20 | if @root_node 21 | BinarySearchTree.insert!(@root_node, value) 22 | else 23 | @root_node = BSTNode.new(value) 24 | end 25 | end 26 | 27 | def find(value) 28 | BinarySearchTree.find!(@root_node, value) 29 | end 30 | 31 | def inorder 32 | BinarySearchTree.inorder!(@root_node) 33 | end 34 | 35 | def postorder 36 | BinarySearchTree.postorder!(@root_node) 37 | end 38 | 39 | def preorder 40 | BinarySearchTree.preorder!(@root_node) 41 | end 42 | 43 | def height 44 | BinarySearchTree.height!(@root_node) 45 | end 46 | 47 | def min 48 | BinarySearchTree.min(@root_node) 49 | end 50 | 51 | def max 52 | BinarySearchTree.max(@root_node) 53 | end 54 | 55 | def delete(value) 56 | BinarySearchTree.delete!(@root_node, value) 57 | end 58 | 59 | def self.insert!(node, value) 60 | # Base case is when node.left or node.right is nil 61 | if node 62 | if value <= node.value 63 | node.left = BinarySearchTree.insert!(node.left, value) 64 | elsif value > node.value 65 | node.right = BinarySearchTree.insert!(node.right, value) 66 | end 67 | node 68 | else 69 | BSTNode.new(value) 70 | end 71 | end 72 | 73 | def self.max(node) 74 | if node 75 | return node if node.right.nil? 76 | BinarySearchTree.max(node.right) 77 | else 78 | nil 79 | end 80 | end 81 | 82 | def self.min(node) 83 | if node 84 | return node if node.left.nil? 85 | BinarySearchTree.min(node.left) 86 | else 87 | nil 88 | end 89 | end 90 | 91 | def self.find!(node, value) 92 | if node 93 | if value < node.value 94 | BinarySearchTree.find!(node.left, value) 95 | elsif node.value < value 96 | BinarySearchTree.find!(node.right, value) 97 | else 98 | node 99 | end 100 | else 101 | nil 102 | end 103 | end 104 | 105 | 106 | def self.inorder!(node) 107 | return [] unless node 108 | arr = [] 109 | arr += BinarySearchTree.inorder!(node.left) 110 | arr << node.value 111 | arr += BinarySearchTree.inorder!(node.right) 112 | arr 113 | end 114 | 115 | def self.preorder!(node) 116 | return [] unless node 117 | arr = [node.value] 118 | arr += BinarySearchTree.preorder!(node.left) 119 | arr += BinarySearchTree.preorder!(node.right) 120 | arr 121 | end 122 | 123 | def self.postorder!(node) 124 | return [] unless node 125 | arr = [] 126 | arr += BinarySearchTree.postorder!(node.left) 127 | arr += BinarySearchTree.postorder!(node.right) 128 | arr << node.value 129 | arr 130 | end 131 | 132 | def self.height!(node) 133 | if node 134 | return 0 if node.left.nil? && node.right.nil? 135 | 136 | depth = [] 137 | if node.left 138 | depth << 1 + BinarySearchTree.height!(node.left) 139 | end 140 | if node.right 141 | depth << 1 + BinarySearchTree.height!(node.right) 142 | end 143 | depth.max 144 | else 145 | -1 146 | end 147 | end 148 | 149 | def self.delete_min!(node) 150 | return nil if node.nil? 151 | if node.left 152 | node.left = BinarySearchTree.delete_min!(node.left) 153 | node 154 | else 155 | node.right 156 | end 157 | end 158 | 159 | def self.delete!(node, value) 160 | return nil if node.nil? 161 | 162 | if value < node.value 163 | node.left = BinarySearchTree.delete!(node.left, value) 164 | elsif node.value < value 165 | node.right = BinarySearchTree.delete!(node.right, value) 166 | else 167 | return node.left if node.right.nil? 168 | return node.right if node.left.nil? 169 | # What is it trying to do? 170 | t = node 171 | node = t.right.min 172 | node.right = BinarySearchTree.delete_min!(t.right) 173 | node.left = t.left 174 | end 175 | end 176 | end 177 | 178 | root = BSTNode.new(5) 179 | tree = BinarySearchTree.new 180 | BinarySearchTree.insert!(root, 1) 181 | BinarySearchTree.insert!(root, 2) 182 | BinarySearchTree.insert!(root, 3) 183 | BinarySearchTree.insert!(root, 4) 184 | console.log(tree.inorder(); 185 | -------------------------------------------------------------------------------- /code/ruby/dfs.rb: -------------------------------------------------------------------------------- 1 | class Vertex 2 | attr_accessor :value, :in_edges, :out_edges 3 | def initialize(value) 4 | @value, @in_edges, @out_edges = value, [], [] 5 | @visited = false 6 | end 7 | 8 | def visited? 9 | @visited 10 | end 11 | 12 | def visit! 13 | @visited = true 14 | end 15 | end 16 | 17 | class Edge 18 | attr_accessor :to_vertex, :from_vertex, :cost 19 | def initialize(from_vertex, to_vertex, cost = 1) 20 | self.from_vertex = from_vertex 21 | self.to_vertex = to_vertex 22 | self.cost = cost 23 | 24 | to_vertex.in_edges << self 25 | from_vertex.out_edges << self 26 | end 27 | 28 | def destroy! 29 | self.to_vertex.out_edges.delete(self) 30 | self.to_vertex = nil 31 | self.from_vertex.in_edges.delete(self) 32 | self.from_vertex = nil 33 | end 34 | end 35 | 36 | def dfs(node, target) 37 | return nil if node.nil? || node.visited? 38 | node.visit! 39 | return node if node.value == target 40 | 41 | node.out_edges.each do |out_edge| 42 | search_result = dfs(out_edge.to_vertex, target) 43 | return search_result unless search_result.nil? 44 | end 45 | 46 | nil 47 | end 48 | 49 | v1 = Vertex.new("Calvin") 50 | v2 = Vertex.new("Steven") 51 | v3 = Vertex.new("Matt") 52 | v4 = Vertex.new("Loki") 53 | e1 = Edge.new(v1, v2) 54 | e6 = Edge.new(v1, v4) 55 | e2 = Edge.new(v2, v3) 56 | e3 = Edge.new(v2, v1) 57 | e4 = Edge.new(v3, v1) 58 | e5 = Edge.new(v3, v4) 59 | 60 | result = dfs(v1, "Loki") 61 | p result.value 62 | p v1.visited? 63 | p v2.visited? 64 | p v3.visited? 65 | p v4.visited? 66 | -------------------------------------------------------------------------------- /code/ruby/dijsktra/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/code/ruby/dijsktra/.DS_Store -------------------------------------------------------------------------------- /code/ruby/dijsktra/dijkstra1.rb: -------------------------------------------------------------------------------- 1 | require_relative 'graph' 2 | 3 | # O(|V|**2 + |E|). 4 | def dijkstra1(source) 5 | locked_in_paths = {} 6 | possible_paths = { 7 | source => {cost: 0, last_edge: nil} 8 | } 9 | # O(|V|) times, it considers all vertices 10 | until possible_paths.empty? 11 | # Find the vertex with minimum cost 12 | vertex = select_possible_path(possible_paths) 13 | locked_in_paths[vertex] = possible_paths[vertex] 14 | # Remove the current vertex from considerations 15 | possible_paths.delete(vertex) 16 | # Update the cost to reach other nodes 17 | update_possible_paths(vertex, locked_in_paths, possible_paths) 18 | end 19 | locked_in_paths 20 | end 21 | 22 | # O(|V|) times, it considers all the vertices 23 | def select_possible_path(possible_paths) 24 | vertex, data = possible_paths.min_by do |vertex, data| 25 | data[:cost] 26 | end 27 | vertex 28 | end 29 | 30 | # O(|E|) times overall 31 | def update_possible_paths(vertex, locked_in_paths, possible_paths) 32 | # vertex refers to the vertex that we are currently looking at 33 | path_to_vertex_cost = locked_in_paths[vertex][:cost] 34 | vertex.out_edges.each do | edge | 35 | neighbor = edge.to_vertex 36 | #Skip if we have already looked at the neighbor 37 | next if locked_in_paths.has_key?(neighbor) 38 | 39 | extended_path_cost = path_to_vertex_cost + edge.cost 40 | if possible_paths.has_key?(neighbor) && 41 | possible_paths[neighbor][:cost] <= extended_path_cost 42 | next 43 | else 44 | possible_paths[neighbor] = { 45 | cost: extended_path_cost, 46 | last_edge: edge 47 | } 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /code/ruby/dijsktra/dijkstra2.rb: -------------------------------------------------------------------------------- 1 | require_relative 'graph' 2 | require_relative 'priority_map' 3 | 4 | # O(|V| + |E|*log(|V|)). 5 | def dijkstra2(source) 6 | locked_in_paths = {} 7 | possible_paths = PriorityMap.new do |data1, data2| 8 | data1[:cost] <=> data2[:cost] 9 | end 10 | possible_paths[source] = { cost: 0, last_edge: nil} 11 | 12 | # O(|V|) times, we need to run through all vertices 13 | until possible_paths.empty? 14 | #O(log(|V|)) for heapifying 15 | vertex, data = possible_paths.extract 16 | locked_in_paths[vertex] = data 17 | update_possible_paths(vertex, locked_in_paths, possible_paths) 18 | end 19 | locked_in_paths 20 | end 21 | 22 | def update_possible_paths(vertex, locked_in_paths, possible_paths) 23 | path_to_vertex_cost = locked_in_paths[vertex][:cost] 24 | # O(|E|) 25 | vertex.out_edges.each do |edge| 26 | neighbor = edge.to_vertex 27 | next if locked_in_paths.has_key?(neighbor) 28 | 29 | extended_path_cost = path_to_vertex_cost + edge.cost 30 | if possible_paths.has_key?(neighbor) && possible_paths[neighbor][:cost] <= extended_path_cost 31 | next 32 | else 33 | # O(log(|V|)) 34 | possible_paths[neighbor] = { 35 | cost: extended_path_cost, 36 | last_edge: edge 37 | } 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /code/ruby/dijsktra/graph.rb: -------------------------------------------------------------------------------- 1 | class Vertex 2 | attr_reader :value, :in_edges, :out_edges 3 | 4 | def initialize(value) 5 | @value, @in_edges, @out_edges = value, [], [] 6 | end 7 | end 8 | 9 | class Edge 10 | attr_reader :to_vertex, :from_vertex, :cost 11 | 12 | def initialize(from_vertex, to_vertex, cost = 1) 13 | self.from_vertex = from_vertex 14 | self.to_vertex = to_vertex 15 | self.cost = cost 16 | 17 | to_vertex.in_edges << self 18 | from_vertex.out_edges << self 19 | end 20 | 21 | def destroy! 22 | self.to_vertex.out_edges.delete(self) 23 | self.to_vertex = nil 24 | self.from_vertex.in_edges.delete(self) 25 | self.from_vertex = nil 26 | end 27 | 28 | protected 29 | attr_writer :from_vertex, :to_vertex, :cost 30 | end 31 | -------------------------------------------------------------------------------- /code/ruby/dijsktra/heap2.rb: -------------------------------------------------------------------------------- 1 | # This is an advanced version of the heap code from before with a 2 | # `reduce!` method. Effectively this just maintains an `index_map` 3 | # that associates values with indices in the heap. 4 | 5 | class BinaryMinHeap 6 | def initialize(&prc) 7 | self.store = [] 8 | # Keeps track of the index a value is stored at. 9 | self.index_map = {} 10 | self.prc = prc || Proc.new { |el1, el2| el1 <=> el2 } 11 | end 12 | 13 | def count 14 | store.length 15 | end 16 | 17 | def empty? 18 | count == 0 19 | end 20 | 21 | def extract 22 | raise "no element to extract" if count == 0 23 | 24 | self.class.swap!(store, index_map, 0, self.count - 1) 25 | 26 | val = store.pop 27 | index_map.delete(val) 28 | 29 | unless empty? 30 | self.class.heapify_down(store, index_map, 0, &prc) 31 | end 32 | 33 | val 34 | end 35 | 36 | def peek 37 | raise "no element to peek" if count == 0 38 | store[0] 39 | end 40 | 41 | def push(val) 42 | store << val 43 | index_map[val] = (store.length - 1) 44 | self.class.heapify_up(store, index_map, self.count - 1, &prc) 45 | end 46 | 47 | def reduce!(val) 48 | index = index_map[val] 49 | self.class.heapify_up(store, index_map, index, &prc) 50 | end 51 | 52 | protected 53 | attr_accessor :index_map, :prc, :store 54 | 55 | public 56 | def self.child_indices(len, parent_index) 57 | # If `parent_index` is the parent of `child_index`: 58 | # 59 | # (1) There are `parent_index` previous nodes to 60 | # `parent_index`. Any children of `parent_index` needs to appear 61 | # after the children of all the nodes preceeding the parent. 62 | # 63 | # (2) Also, since the tree is full, every preceeding node will 64 | # have two children before the parent has any children. This means 65 | # there are `2 * parent_index` child nodes before the first child 66 | # of `parent_index`. 67 | # 68 | # (3) Lastly there is also the root node, which is not a child of 69 | # anyone. Therefore, there are a total of `2 * parent_index + 1` 70 | # nodes before the first child of `parent_index`. 71 | # 72 | # (4) Therefore, the children of parent live at `2 * parent_index 73 | # + 1` and `2 * parent_index + 2`. 74 | 75 | [2 * parent_index + 1, 2 * parent_index + 2].select do |idx| 76 | # Only keep those in range. 77 | idx < len 78 | end 79 | end 80 | 81 | def self.parent_index(child_index) 82 | # If child_index is odd: `child_index == 2 * parent_index + 1` 83 | # means `parent_index = (child_index - 1) / 2`. 84 | # 85 | # If child_index is even: `child_index == 2 * parent_index + 2` 86 | # means `parent_index = (child_index - 2) / 2`. Note that, because 87 | # of rounding, when child_index is even: `(child_index - 2) / 2 == 88 | # (child_index - 1) / 2`. 89 | 90 | raise "root has no parent" if child_index == 0 91 | (child_index - 1) / 2 92 | end 93 | 94 | def self.heapify_down(array, index_map, parent_idx, len = array.length, &prc) 95 | prc ||= Proc.new { |el1, el2| el1 <=> el2 } 96 | 97 | l_child_idx, r_child_idx = child_indices(len, parent_idx) 98 | 99 | parent_val = array[parent_idx] 100 | 101 | children = [] 102 | children << array[l_child_idx] if l_child_idx 103 | children << array[r_child_idx] if r_child_idx 104 | 105 | if children.all? { |child| prc.call(parent_val, child) <= 0 } 106 | # Leaf or both children_vals <= parent_val. As a convenience, 107 | # return the modified array. 108 | return array 109 | end 110 | 111 | # Choose smaller of two children. 112 | swap_idx = nil 113 | if children.length == 1 114 | swap_idx = l_child_idx 115 | else 116 | swap_idx = 117 | prc.call(children[0], children[1]) <= 0 ? l_child_idx : r_child_idx 118 | end 119 | 120 | swap!(array, index_map, parent_idx, swap_idx) 121 | heapify_down(array, index_map, swap_idx, len, &prc) 122 | end 123 | 124 | def self.heapify_up(array, index_map, child_idx, len = array.length, &prc) 125 | prc ||= Proc.new { |el1, el2| el1 <=> el2 } 126 | 127 | # As a convenience, return array 128 | return array if child_idx == 0 129 | 130 | parent_idx = parent_index(child_idx) 131 | child_val, parent_val = array[child_idx], array[parent_idx] 132 | if prc.call(child_val, parent_val) >= 0 133 | # Heap property valid! 134 | return array 135 | else 136 | swap!(array, index_map, parent_idx, child_idx) 137 | heapify_up(array, index_map , parent_idx, len, &prc) 138 | end 139 | end 140 | 141 | def self.swap!(array, index_map, parent_idx, child_idx) 142 | parent_val, child_val = array[parent_idx], array[child_idx] 143 | 144 | array[parent_idx], array[child_idx] = child_val, parent_val 145 | index_map[parent_val], index_map[child_val] = child_idx, parent_idx 146 | end 147 | end 148 | 149 | # TESTING 150 | # Todo: Write RSpec tests! 151 | 152 | class NumWrapper 153 | attr_accessor :value 154 | def initialize(value) 155 | self.value = value 156 | end 157 | end 158 | 159 | def main 160 | heap = BinaryMinHeap.new { |v1, v2| v1.value <=> v2.value } 161 | heap.push(NumWrapper.new(5)) 162 | heap.push(NumWrapper.new(7)) 163 | heap.push(NumWrapper.new(3)) 164 | heap.push(NumWrapper.new(9)) 165 | heap.push(NumWrapper.new(1)) 166 | 167 | vals = (0...5).map { heap.extract.value } 168 | p vals 169 | fail unless vals == [1, 3, 5, 7, 9] 170 | 171 | heap = BinaryMinHeap.new { |v1, v2| v1.value <=> v2.value } 172 | heap.push(nw1 = NumWrapper.new(5)) 173 | heap.push(nw2 = NumWrapper.new(7)) 174 | heap.push(nw3 = NumWrapper.new(3)) 175 | heap.push(nw4 = NumWrapper.new(9)) 176 | heap.push(nw5 = NumWrapper.new(1)) 177 | 178 | nw1.value = -1 179 | heap.reduce!(nw1) 180 | 181 | nw4.value = 4 182 | heap.reduce!(nw4) 183 | 184 | vals = (0...5).map { heap.extract.value } 185 | p vals 186 | fail unless vals == [-1, 1, 3, 4, 7] 187 | end 188 | 189 | main if __FILE__ == $PROGRAM_NAME 190 | -------------------------------------------------------------------------------- /code/ruby/dijsktra/priority_map.rb: -------------------------------------------------------------------------------- 1 | require_relative 'heap2' 2 | 3 | class PriorityMap 4 | def initialize(&prc) 5 | @map = {} 6 | @queue = BinaryMinHeap.new do |key1, key2| 7 | prc.call(@map[key1], @map[key2]) 8 | end 9 | end 10 | 11 | def [](key) 12 | return nil unless @map.has_key?(key) 13 | @map[key] 14 | end 15 | 16 | def []=(key, value) 17 | if @map.has_key?(key) 18 | update(key, value) 19 | else 20 | insert(key, value) 21 | end 22 | end 23 | 24 | def count 25 | @map.count 26 | end 27 | 28 | def empty? 29 | count == 0 30 | end 31 | 32 | def extract 33 | #key is the vertex, while value is the cost 34 | key = @queue.extract 35 | value = @map.delete(key) 36 | [key, value] 37 | end 38 | 39 | def has_key?(key) 40 | @map.has_key?(key) 41 | end 42 | 43 | protected 44 | attr_accessor :map, :queue 45 | 46 | def insert(key, value) 47 | @map[key] = value 48 | @queue.push(key) 49 | end 50 | 51 | def update(key, value) 52 | throw "key does not exist" unless @map.has_key?(key) 53 | @map[key] = value 54 | @queue.reduce!(key) 55 | nil 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /code/ruby/dynamic_array.rb: -------------------------------------------------------------------------------- 1 | require_relative "static_array" 2 | 3 | class DynamicArray 4 | attr_reader :length 5 | 6 | def initialize(size = 8) 7 | @store = StaticArray.new(size) 8 | @length = 0 9 | @capacity = size 10 | end 11 | 12 | # O(1) 13 | def [](index) 14 | if index >= length 15 | raise "index out of bounds" 16 | end 17 | @store[index] 18 | end 19 | 20 | # O(1) 21 | def []=(index, value) 22 | @store[index] = value 23 | end 24 | 25 | # O(1) 26 | def pop 27 | if @length == 0 28 | raise "index out of bounds" 29 | end 30 | @length -= 1 31 | @store[@length] 32 | end 33 | 34 | # O(1) ammortized; O(n) worst case. Variable because of the possible 35 | # resize. 36 | def push(val) 37 | if @length == @capacity 38 | self.resize! 39 | end 40 | @store[@length] = val 41 | @length += 1 42 | end 43 | 44 | # O(n): has to shift over all the elements. 45 | def shift 46 | if @length == 0 47 | raise "index out of bounds" 48 | end 49 | 50 | returnVal = @store[0] 51 | i = 1 52 | while i < @length do 53 | @store[i - 1] = @store[i] 54 | i += 1 55 | end 56 | @length -= 1 57 | returnVal 58 | end 59 | 60 | # O(n): has to shift over all the elements. 61 | def unshift(val) 62 | if @length == @capacity 63 | self.resize! 64 | end 65 | i = @length 66 | while i >= 0 do 67 | @store[i + 1] = @store[i] 68 | i -= 1 69 | end 70 | @length += 1 71 | @store[0] = val 72 | end 73 | 74 | protected 75 | attr_accessor :capacity, :store 76 | attr_writer :length 77 | 78 | def check_index(index) 79 | end 80 | 81 | # O(n): has to copy over all the elements to the new store. 82 | def resize! 83 | @capacity *= 2 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /code/ruby/fsa.rb: -------------------------------------------------------------------------------- 1 | # Finite State Automata 2 | 3 | # Assuming we are only allowing a, b, c states, hence "finite states" 4 | def transition_table(pattern, radix) 5 | table = Hash.new 6 | (0..pattern.length).each do |i| 7 | table["state:#{i}"] = {pattern: pattern[0...i]} 8 | end 9 | 10 | (0...pattern.length).each do |i| 11 | radix.each do |char| 12 | str = table["state:#{i}"][:pattern] + char 13 | discard_num = 0 14 | (i+1).downto(0) do |j| 15 | if table["state:#{j}"][:pattern] == str.chars.drop(discard_num).join 16 | table["state:#{i}"][char] = "state:#{j}" 17 | next 18 | end 19 | discard_num += 1 20 | end 21 | end 22 | end 23 | 24 | table 25 | end 26 | 27 | def string_match(string, pattern, radix) 28 | table = transition_table(pattern, radix) 29 | num_of_states = table.keys.length - 1 30 | curr_state = "state:0" 31 | string.chars.each do |char| 32 | curr_state = table[curr_state][char] 33 | return true if curr_state == "state:#{num_of_states}" 34 | end 35 | false 36 | end 37 | 38 | puts transition_table("acgt", ['a','c','g', 't']); 39 | puts string_match("acgacgacgaccgtgctgactacggtttacg", "acgt", ['a', 'c', 'g', 't']) 40 | -------------------------------------------------------------------------------- /code/ruby/graph.rb: -------------------------------------------------------------------------------- 1 | class Vertex 2 | attr_reader :in_edges, :out_edges, :value 3 | 4 | def initialize(value) 5 | @value = value 6 | @in_edges = [] 7 | @out_edges = [] 8 | end 9 | 10 | def add_in_edge(edge) 11 | @in_edges << edge 12 | end 13 | 14 | def remove_in_edge(edge) 15 | @in_edges.each_index do |idx| 16 | if @in_edges[idx] == edge 17 | @in_edges.delete_at(idx) 18 | end 19 | end 20 | end 21 | 22 | def add_out_edge(edge) 23 | @out_edges << edge 24 | end 25 | 26 | def remove_out_edge(edge) 27 | @out_edges.each_index do |idx| 28 | if @out_edges[idx] == edge 29 | @out_edges.delete_at(idx) 30 | end 31 | end 32 | end 33 | 34 | end 35 | 36 | class Edge 37 | attr_reader :from_vertex, :to_vertex, :cost 38 | 39 | def initialize(from_vertex, to_vertex, cost = 1) 40 | @from_vertex = from_vertex 41 | @to_vertex = to_vertex 42 | @cost = cost 43 | from_vertex.add_out_edge(self) 44 | to_vertex.add_in_edge(self) 45 | end 46 | 47 | def destroy! 48 | @from_vertex.remove_out_edge(self) 49 | @from_vertex = nil 50 | @to_vertex.remove_in_edge(self) 51 | @to_vertex = nil 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /code/ruby/heap.rb: -------------------------------------------------------------------------------- 1 | require 'byebug' 2 | 3 | class BinaryMinHeap 4 | def initialize(&prc) 5 | @store = [] 6 | end 7 | 8 | def count 9 | @store.length 10 | end 11 | 12 | def extract 13 | temp = @store[0] 14 | @store[0] = @store[-1] 15 | @store[-1] = temp 16 | extracted_val = @store.pop 17 | BinaryMinHeap.heapify_down(@store, 0) 18 | extracted_val 19 | end 20 | 21 | def peek 22 | end 23 | 24 | def push(val) 25 | @store.push(val) 26 | BinaryMinHeap.heapify_up(@store, @store.length - 1) 27 | end 28 | 29 | protected 30 | attr_accessor :prc, :store 31 | 32 | public 33 | def self.child_indices(len, parent_index) 34 | children = [] 35 | first_child = parent_index*2 + 1 36 | children << first_child if first_child < len 37 | second_child = parent_index*2 + 2 38 | children << second_child if second_child < len 39 | children 40 | end 41 | 42 | def self.parent_index(child_index) 43 | raise "root has no parent" if child_index == 0 44 | (child_index - 1)/2 45 | end 46 | 47 | def self.heapify_down(array, parent_idx, len = array.length, &prc) 48 | unless prc 49 | prc = Proc.new {|parent, child| parent <=> child} 50 | end 51 | 52 | children = BinaryMinHeap.child_indices(len, parent_idx) 53 | return array if children.length == 0 54 | 55 | case children.length 56 | when 0 57 | return array 58 | when 1 59 | if prc.call(array[parent_idx], array[children[0]]) == 1 60 | temp = array[parent_idx] 61 | array[parent_idx] = array[children[0]] 62 | array[children[0]] = temp 63 | end 64 | when 2 65 | if prc.call(array[parent_idx],array[children[0]]) == 1 || prc.call(array[parent_idx],array[children[1]]) == 1 66 | if prc.call(array[children[0]],array[children[1]]) == -1 67 | temp = array[parent_idx] 68 | array[parent_idx] = array[children[0]] 69 | array[children[0]] = temp 70 | else 71 | temp = array[parent_idx] 72 | array[parent_idx] = array[children[1]] 73 | array[children[1]] = temp 74 | end 75 | end 76 | end 77 | BinaryMinHeap.heapify_down(array, children[0], &prc) 78 | BinaryMinHeap.heapify_down(array, children[1], &prc) if children.length == 2 79 | end 80 | 81 | def self.heapify_up(array, child_idx, len = array.length, &prc) 82 | unless prc 83 | prc = Proc.new {|parent, child| parent <=> child} 84 | end 85 | # base case 86 | return array if child_idx == 0; 87 | 88 | parent_idx = BinaryMinHeap.parent_index(child_idx) 89 | if prc.call(array[parent_idx], array[child_idx]) == 1 90 | temp = array[parent_idx] 91 | array[parent_idx] = array[child_idx] 92 | array[child_idx] = temp 93 | end 94 | child_idx = parent_idx 95 | BinaryMinHeap.heapify_up(array, child_idx, &prc) 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /code/ruby/heap_sort.rb: -------------------------------------------------------------------------------- 1 | require_relative "heap" 2 | require 'byebug' 3 | class Array 4 | def heap_sort! 5 | heap = BinaryMinHeap.new 6 | self.each do |el| 7 | heap.push(el) 8 | end 9 | self.each_index do |idx| 10 | self[idx] = heap.extract 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /code/ruby/lcs.rb: -------------------------------------------------------------------------------- 1 | # Longest Common Subsequence 2 | require 'benchmark' 3 | # DO NOT CONFUSE subsequence with substrings 4 | # Letters in a subsequence do not need to appear consecutive, while 5 | # substrings do 6 | 7 | # Using dynamic programming by breaking problem into subproblems and 8 | # store them in a matrix 9 | def dynamic_lcs(str1, str2) 10 | c = [] 11 | 0.upto(str1.length) do |i| 12 | row = [] 13 | 0.upto(str2.length) do |j| 14 | row << {value: 0, subseq: ""} 15 | end 16 | c << row 17 | end 18 | 19 | 0.upto(str1.length) do |i| 20 | 0.upto(str2.length) do |j| 21 | unless i == 0 || j == 0 22 | if str1[i-1] != nil && str2[j-1] != nil && str1[i-1] == str2[j-1] 23 | c[i][j][:value] = c[i-1][j-1][:value] + 1 24 | c[i][j][:subseq] = c[i-1][j-1][:subseq] + str1[i-1] 25 | elsif c[i][j-1][:value] > c[i-1][j][:value] 26 | c[i][j][:value] = c[i][j-1][:value] 27 | c[i][j][:subseq] = c[i][j-1][:subseq] 28 | else 29 | c[i][j][:value] = c[i-1][j][:value] 30 | c[i][j][:subseq] = c[i-1][j][:subseq] 31 | end 32 | end 33 | end 34 | end 35 | printMatrix(c) 36 | c[str1.length][str2.length] 37 | end 38 | 39 | def printMatrix(mat) 40 | 0.upto(mat.length - 1) do |row| 41 | str = "" 42 | 0.upto(mat[row].length - 1) do |col| 43 | str << mat[row][col][:value].to_s + " " 44 | end 45 | puts str 46 | end 47 | end 48 | 49 | def brute_force_lcs(str1, str2) 50 | subseqs1 = subseqs(str1) 51 | subseqs2 = subseqs(str2) 52 | longest_subseq = "" 53 | subseqs1.each do |sub1| 54 | subseqs2.each do |sub2| 55 | if sub1 == sub2 && sub1.length > longest_subseq.length 56 | longest_subseq = sub1 57 | end 58 | end 59 | end 60 | longest_subseq 61 | end 62 | 63 | def subseqs(str) 64 | return [str] if str.length == 1 65 | subseqs(str[0...str.length - 1]).map {|el| el + str[str.length - 1]} + 66 | subseqs(str[0...str.length - 1]) + 67 | [str[-1]] 68 | end 69 | 70 | dna_letters = ["a", "c", "g", "t"] 71 | dna1 = "" 72 | dna2 = "" 73 | 23.times do |i| 74 | dna1 << dna_letters[rand(4)] 75 | dna2 << dna_letters[rand(4)] 76 | end 77 | # dna1 = "acbcefg" 78 | # dna2 = "abcdaefg" 79 | 80 | # puts dna1 81 | # puts dna2 82 | # puts Benchmark.measure { p dynamic_lcs(dna1, dna2) } 83 | # puts Benchmark.measure { p brute_force_lcs(dna1, dna2) } 84 | 85 | #======================================================================= 86 | # Longest Common Substrings 87 | # Brute force 88 | def substrings(str) 89 | subseq = [] 90 | 0.upto(str.length - 1) do |i| 91 | (i).upto(str.length - 1) do |j| 92 | subseq << str[i..j] 93 | end 94 | end 95 | subseq 96 | end 97 | 98 | # Find longest common substrings with brute force 99 | def longest_common_substr(str1, str2) 100 | curr_longest = "" 101 | substrs1 = substrings(str1) 102 | substrs2 = substrings(str2) 103 | substrs1.each do |sub1| 104 | substrs2.each do |sub2| 105 | if sub1 == sub2 && sub1.length > curr_longest.length 106 | curr_longest = sub1 107 | end 108 | end 109 | end 110 | curr_longest 111 | end 112 | 113 | puts Benchmark.measure { substrings(dna1) } 114 | puts Benchmark.measure { subseqs(dna1) } 115 | -------------------------------------------------------------------------------- /code/ruby/linked_list.rb: -------------------------------------------------------------------------------- 1 | require 'byebug' 2 | 3 | class Link 4 | attr_accessor :key, :val, :next, :prev 5 | 6 | def initialize(key = nil, val = nil) 7 | @key = key 8 | @val = val 9 | @next = nil 10 | @prev = nil 11 | end 12 | 13 | def to_s 14 | "#{@key}: #{@val}" 15 | end 16 | end 17 | 18 | class LinkedList 19 | include Enumerable 20 | 21 | def initialize 22 | @head = Link.new 23 | @tail = Link.new 24 | @head.next = @tail 25 | @tail.prev = @head 26 | end 27 | 28 | def [](i) 29 | each_with_index { |link, j| return link if i == j } 30 | nil 31 | end 32 | 33 | def first 34 | empty? ? nil : @head.next 35 | end 36 | 37 | def last 38 | empty? ? nil : @tail.prev 39 | end 40 | 41 | def empty? 42 | @head.next == @tail 43 | end 44 | 45 | def get(key) 46 | each { |link| return link.val if link.key == key } 47 | nil 48 | end 49 | 50 | def include?(key) 51 | any? { |link| link.key == key } 52 | end 53 | 54 | def insert(key, val) 55 | each { |link| return link.val = val if link.key == key } 56 | 57 | new_link = Link.new(key, val) 58 | current_last = @tail.prev 59 | 60 | current_last.next = new_link 61 | new_link.prev = current_last 62 | new_link.next = @tail 63 | @tail.prev = new_link 64 | 65 | new_link 66 | end 67 | 68 | def remove(key) 69 | each do |link| 70 | if link.key == key 71 | link.prev.next = link.next 72 | link.next.prev = link.prev 73 | link.next, link.prev = nil, nil 74 | return link.val 75 | end 76 | end 77 | 78 | nil 79 | end 80 | 81 | def each 82 | current_link = @head.next 83 | until current_link == @tail 84 | yield current_link 85 | current_link = current_link.next 86 | end 87 | end 88 | 89 | def to_s 90 | inject([]) { |acc, link| acc << "[#{link.key}, #{link.val}]" }.join(", ") 91 | end 92 | end 93 | 94 | class BSTNode 95 | attr_accessor :left, :right 96 | attr_reader :value 97 | 98 | def initialize(value) 99 | @value = value 100 | @left = nil 101 | @right = nil 102 | end 103 | end 104 | 105 | def depth_lists(root) 106 | linkedlist_arr = [] 107 | current_list = LinkedList.new 108 | current_list.insert(root.value, root) unless root.nil? 109 | until current_list.empty? 110 | linkedlist_arr << current_list 111 | parents = current_list 112 | current_list = LinkedList.new 113 | parents.each do |parent| 114 | parentNode = parent.val 115 | current_list.insert(parentNode.left.value, parentNode.left) unless parentNode.left.nil? 116 | current_list.insert(parentNode.right.value, parentNode.right) unless parentNode.right.nil? 117 | end 118 | end 119 | linkedlist_arr 120 | end 121 | 122 | rootNode = BSTNode.new(4) 123 | rootNode.left = BSTNode.new(2) 124 | rootNode.left.left = BSTNode.new(1) 125 | rootNode.left.right = BSTNode.new(3) 126 | rootNode.right = BSTNode.new(6) 127 | rootNode.right.left = BSTNode.new(5) 128 | rootNode.right.right = BSTNode.new(7) 129 | depth_lists(rootNode) 130 | -------------------------------------------------------------------------------- /code/ruby/quick_sort.rb: -------------------------------------------------------------------------------- 1 | class QuickSort 2 | # Quick sort has average case time complexity O(nlogn), but worst 3 | # case O(n**2). 4 | 5 | # Not in-place. Uses O(n) memory. 6 | def self.sort1(array) 7 | return array if array.empty? 8 | new_pivot = rand(array.length) 9 | array[0], array[new_pivot] = array[new_pivot], array[0] 10 | 11 | pivot = array.first 12 | 13 | left = array.select { |el| pivot > el } 14 | middle = array.select { |el| pivot == el } 15 | right = array.select { |el| pivot < el } 16 | 17 | sort1(left) + middle + sort1(right) 18 | end 19 | 20 | # In-place. 21 | def self.sort2!(array, start = 0, length = array.length, &prc) 22 | prc ||= Proc.new { |el1, el2| el1 <=> el2 } 23 | 24 | return array if length < 2 25 | 26 | pivot_idx = partition(array, start, length, &prc) 27 | 28 | left_length = pivot_idx - start 29 | right_length = length - (left_length + 1) 30 | sort2!(array, start, left_length, &prc) 31 | sort2!(array, pivot_idx + 1, right_length, &prc) 32 | 33 | array 34 | end 35 | 36 | def self.partition(array, start, length, &prc) 37 | prc ||= Proc.new { |el1, el2| el1 <=> el2 } 38 | pivot_idx, pivot = start, array[start] 39 | ((start + 1)...(start + length)).each do |idx| 40 | val = array[idx] 41 | if prc.call(pivot, val) < 1 42 | # where it is. 43 | else 44 | array[idx] = array[pivot_idx + 1] 45 | array[pivot_idx + 1] = pivot 46 | array[pivot_idx] = val 47 | 48 | pivot_idx += 1 49 | end 50 | 51 | end 52 | pivot_idx 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /code/ruby/radix_sort.rb: -------------------------------------------------------------------------------- 1 | require 'byebug' 2 | class LSD 3 | # Assuming all strings in the array have same length, which is str_length 4 | def self.sort(str_arr, str_length) 5 | # assuming extended ASCII characters, there are 256 of them 6 | # use Ruby's ord method 7 | radix = 256 8 | (str_length - 1).downto(0) do | d | 9 | buffer = [] 10 | count = [] 11 | 12 | # Set everything to be zero, since can't set it to zero by default 13 | (0...radix).each do |idx| 14 | count[idx] = 0 15 | end 16 | 17 | str_arr.each do |str| 18 | idx = str[d].ord 19 | count[idx + 1] += 1 20 | end 21 | 22 | (0...radix - 1).each do |idx| 23 | count[idx + 1] += count[idx] 24 | end 25 | # the count array will indicate the appropriate index position 26 | # for the sorted string array 27 | (0...str_arr.length).each do |idx| 28 | i = str_arr[idx][d].ord 29 | buffer[count[i]] = str_arr[idx] 30 | count[i] += 1 31 | end 32 | 33 | str_arr = buffer 34 | end 35 | str_arr 36 | end 37 | end 38 | 39 | def key_indexed_count(arr) 40 | # ASCII character has a radix of 256 41 | radix = 256 42 | count = [] 43 | result = [] 44 | arr.each do |el| 45 | idx = el.ord 46 | if count[idx] 47 | count[idx] += 1 48 | else 49 | count[idx] = 1 50 | end 51 | end 52 | 53 | (0...radix).each do |idx| 54 | if count[idx] 55 | until count[idx] == 0 56 | result << idx.chr 57 | count[idx] -= 1 58 | end 59 | end 60 | end 61 | result 62 | end 63 | 64 | letters = ["d","a","c","f","f","b","d","b","w","m","h","j"] 65 | arr = ["calvin", "hellow","steven", ] 66 | nums = ["123","412","121","001","415","990"] 67 | 68 | p key_indexed_count(letters) 69 | p LSD.sort(arr, 3) 70 | p LSD.sort(nums, 3) 71 | -------------------------------------------------------------------------------- /code/ruby/ring_buffer.rb: -------------------------------------------------------------------------------- 1 | require_relative "static_array" 2 | require 'byebug' 3 | class RingBuffer 4 | attr_reader :length 5 | 6 | def initialize(size = 8) 7 | @store = StaticArray.new(size) 8 | @start_idx = 0 9 | @length = 0 10 | @capacity = size 11 | end 12 | 13 | # O(1) 14 | def [](index) 15 | if index >= length 16 | raise "index out of bounds" 17 | end 18 | logical_index = self.check_index(index) 19 | @store[logical_index] 20 | end 21 | 22 | # O(1) 23 | def []=(index, val) 24 | logical_index = self.check_index(index) 25 | @store[logical_index] = val 26 | end 27 | 28 | # O(1) 29 | def pop 30 | if @length == 0 31 | raise "index out of bounds" 32 | end 33 | @length -= 1 34 | last_index = self.check_index(@length) 35 | @store[last_index] 36 | end 37 | 38 | # O(1) ammortized 39 | def push(val) 40 | if @length == @capacity 41 | self.resize! 42 | end 43 | last_index = self.check_index(@length) 44 | @store[last_index] = val 45 | @length += 1 46 | end 47 | 48 | # O(1) 49 | def shift 50 | if @length == 0 51 | raise "index out of bounds" 52 | end 53 | val = @store[@start_idx] 54 | @start_idx = (@start_idx + 1) % @capacity 55 | @length -= 1 56 | val 57 | end 58 | 59 | # O(1) ammortized 60 | def unshift(val) 61 | if @length == @capacity 62 | self.resize! 63 | end 64 | @length += 1 65 | @start_idx = (@start_idx - 1) % @capacity 66 | @store[@start_idx] = val 67 | end 68 | 69 | protected 70 | attr_accessor :capacity, :start_idx, :store 71 | attr_writer :length 72 | 73 | def check_index(index) 74 | (@start_idx + index) % @capacity 75 | end 76 | 77 | def resize! 78 | new_store = StaticArray.new(2*@capacity) 79 | i = 0 80 | while i < @length 81 | new_store[i] = @store[(@start_idx + i) % capacity] 82 | i += 1 83 | end 84 | @capacity *= 2 85 | @start_idx = 0 86 | @store = new_store 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /code/ruby/static_array.rb: -------------------------------------------------------------------------------- 1 | # This class just dumbs down a regular Array to be staticly sized. 2 | class StaticArray 3 | def initialize(length) 4 | @store = [] 5 | end 6 | 7 | # O(1) 8 | def [](index) 9 | @store[index] 10 | end 11 | 12 | # O(1) 13 | def []=(index, value) 14 | @store[index] = value 15 | end 16 | 17 | protected 18 | attr_accessor :store 19 | end 20 | -------------------------------------------------------------------------------- /code/ruby/topological_sort.rb: -------------------------------------------------------------------------------- 1 | require_relative 'graph' 2 | require 'byebug' 3 | # Implementing topological sort using both Khan's and Tarian's algorithms 4 | 5 | def topological_sort(vertices) 6 | sorted_vertices = [] 7 | zero_in_degree_queue = [] 8 | in_edge_count = Hash.new 9 | 10 | # O(|V|) 11 | vertices.each do |vertex| 12 | in_edge_count[vertex] = vertex.in_edges.length 13 | if vertex.in_edges.length == 0 14 | zero_in_degree_queue << vertex 15 | end 16 | end 17 | 18 | # O(|V| + |E|) 19 | # goes through all the nodes and edges 20 | while zero_in_degree_queue.length > 0 21 | node = zero_in_degree_queue.shift 22 | node.out_edges.each do |edge| 23 | neighbor = edge.to_vertex 24 | # edge.destroy! 25 | in_edge_count[neighbor] -= 1 26 | if in_edge_count[neighbor] == 0 27 | zero_in_degree_queue << neighbor 28 | end 29 | end 30 | sorted_vertices << node 31 | end 32 | sorted_vertices 33 | end 34 | -------------------------------------------------------------------------------- /code/ruby/trie.rb: -------------------------------------------------------------------------------- 1 | require 'byebug' 2 | class TrieNode 3 | attr_accessor :children, :value 4 | attr_reader :letter, :parent 5 | def initialize(parent = nil, value = nil, letter = nil) 6 | @value = value 7 | @parent = parent 8 | @children = Hash.new 9 | @letter = letter 10 | end 11 | end 12 | 13 | class Trie 14 | def initialize 15 | @root = TrieNode.new 16 | end 17 | 18 | def insert(key, value) 19 | Trie.insert!(@root, key, value) 20 | end 21 | 22 | def find(key) 23 | Trie.find!(@root, key) 24 | end 25 | 26 | def delete(key) 27 | Trie.delete!(@root, key) 28 | end 29 | 30 | def self.insert!(node,key,value) 31 | return nil if key.nil? 32 | if key.length == 0 33 | node.value = value 34 | else 35 | node.children[key[0]] = TrieNode.new(node, nil, key[0]) unless node.children[key[0]] 36 | Trie.insert!(node.children[key[0]], key[1..-1], value) 37 | end 38 | end 39 | 40 | def self.find!(node, key) 41 | return nil if node.nil? 42 | if key.length == 0 43 | node.value 44 | else 45 | Trie.find!(node.children[key[0]], key[1..-1]) 46 | end 47 | end 48 | 49 | def self.delete!(node, key) 50 | return nil if node.nil? 51 | if key.length == 0 52 | node.value = nil 53 | Trie.clean_up_nodes!(node) 54 | else 55 | Trie.delete!(node.children[key[0]], key[1..-1]) 56 | end 57 | end 58 | 59 | def self.clean_up_nodes!(node) 60 | curr_node = node.parent 61 | while curr_node.children.keys == 1 62 | curr_node.children = Hash.new 63 | latest_letter = curr_node.letter 64 | curr_node = curr_node.parent 65 | end 66 | curr_node.children[latest_letter] = nil 67 | end 68 | end 69 | 70 | test_trie = Trie.new 71 | test_trie.insert("cal", {name: "cal", age: 24, weight: 155}) 72 | test_trie.insert("cat", {name: "cat", age: 20, weight: 180}) 73 | # test_trie.insert("cat", {age: 5, weight: 20}) 74 | # test_trie.insert("carmen", {age: 25, weight: 110}) 75 | # test_trie.insert("steven", {age: 31, weight: 140}) 76 | p test_trie.find("cat") 77 | test_trie.delete("cat") 78 | p test_trie.find("cat") 79 | p test_trie.find("cal") 80 | -------------------------------------------------------------------------------- /doc/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/doc/.DS_Store -------------------------------------------------------------------------------- /doc/algorithmic_design_interview.md: -------------------------------------------------------------------------------- 1 | # Algorithm Design Canvas 2 | The algorithm design canvas captures the process for tackling algorithm design 3 | problems. It's the most convenient way to represent algorithmic thinking. 4 | Every algorithmic problem, big or small, easy or hard, should eventually 5 | end up as a completed canvas. 6 | 7 | ![canvas] 8 | [canvas]: http://www.hiredintech.com/data/uploads/the-algorithm-design-canvas.png 9 | 10 | ## 1. Constraints 11 | How large the input array can be? Can the input string contain unicode characters? 12 | Is the robot allowed to make diagonal moves in the maze, can graph have negative 13 | edges? 14 | 15 | Asking interviewer the right question will lead to insightful constraints 16 | 17 | 1. Strings, Arrays, and Numbers 18 | * How many elements can be in the array? 19 | * How large can each element be? If it's a string how long? if it's a number, 20 | what is the minimum and maximum value? 21 | * What is in each element? If it's a number, is it an integer or float? If it's 22 | a string, is it a single byte or multibyte (unicode)? 23 | * If the problem involves finding a subsequence, does "subsequence" mean 24 | that the elements must be adjacent or is there no such requirement? 25 | * Does the array contain unique numbers or can they be repeated? 26 | 27 | 2. Grids/Mazes 28 | * For problems where some actor (e.g. robot) is moving in a grid or a maze, 29 | what moves are allowed? Can the robot move diagonally (hence 8 valid moves), 30 | or only horizontally/vertically (hence only 4 valid moves)? 31 | * Are all cells in the grid allowed? Or can there be obstacles? 32 | * If the actor is trying to get from A to B, are A and B guaranteed to be 33 | different from each other? 34 | * If the actor is trying to get from A to B, is it guaranteed that there's 35 | a path between the two cells? 36 | 37 | 3. Graphs 38 | * How many nodes can the graph have? 39 | * How many edges can the graph have? 40 | * If the edges have weights, what is the range for the weights? 41 | * Can there be loops in the graph? Can there be negative-sum loops in the graph? 42 | * Is the graph directed or undirected? 43 | * Does the graph have multiple edges and/or self loops? 44 | 45 | 4. Return Values 46 | * What should my method return? 47 | * If there are multiple solutions to the problem, which one should be returned? 48 | * If it should return multiple values, do you have any preference on what to return? 49 | * What should I return if the input is invalid? 50 | * What to return if I can't find the element? 51 | 52 | ## 2. Ideas 53 | After you've identified all the constraints, you go into idea generation. 54 | Typically during interview you discuss 1 ~ 3 ideas. Often times you start 55 | with one, explain it to the interviewer, and then move on to a better idea. 56 | 57 | ### Simplify the task 58 | "A map of streets is given, which has the shape of a rectangular grid with N columns 59 | and M rows. At the intersections of these streets there are people. They all want to meet 60 | at one single intersection. The goal is to choose such an intersection, which 61 | minimizes the total walking distance of all people. Remember that they can only 62 | walk along the streets (the so called "Manhattan distance")" 63 | 64 | So how can we approach this problem? 65 | Imagine that we only have one street and people are at various positions 66 | on that street. They will be looking for an optimal place to meet on this 67 | street. Can you solve this problem? It turns out to be much easier than the 68 | 2D version. You just have to find the median value of all people's positions 69 | on the street and this is an optimal answer. 70 | 71 | Now if we go back to the original problem, we can notice that finding 72 | the X and Y coordinates of the meeting point are two independent tasks. 73 | This is because of teh way people move - Manhattan distance. This leads us 74 | to the final conclusion that we have to solve two 1D problems. 75 | 76 | ### Try a few example 77 | You may start noticing patterns if you try to solve a few sample inputs that 78 | you create. It is okay to tell the interviewer that you would like to try 79 | writing down a few examples in order to try to find some pattern. 80 | 81 | Here is a sample problem: 82 | "There are N + 1 parking spots, numbered from 0 to N. There are N cars numbered 83 | from 1 to N parked in various parking spots with one left empty. Reorder the cars 84 | so that car #1 is in spot #1, car #2 is in spot #2 and so on. Spot #0 will remain 85 | empty. The only allowed operation is to take a car and move it to the free spot." 86 | 87 | The key is, which car to move. 88 | 89 | ### Think of suitable data structures 90 | For some problems it is more or less apparent that some sort of data structure will 91 | do the job. If you start to get this feeling think about the data structures 92 | you know about and try to apply them and see if they fit. 93 | 94 | Example question: 95 | "Design a data structure, which supports several operations: insert a number 96 | with O(log n), return the median element with O(1), delete the median element 97 | with O(log n), where n is the number of elements in the data structure" 98 | 99 | We can use two heaps, one stores the one half smallest numbers and other is for the 100 | other half biggest numbers. 101 | 102 | ### Think about related problems that you know 103 | If you see a problem and cannot think of a solution, try to remember another 104 | problem, which looks like it. If there is such, if its solution can somehow be 105 | adjusted to work for the problem at hand. This can sometimes mislead you 106 | but many problems are related, so it could also get you out of the situation. 107 | 108 | ## 3. Complexities 109 | ### Why is complexity so important? 110 | Solving problem is not just about finding a way to compute the correct answer. 111 | The solution should work quickly enough and with reasonable memory usage. 112 | Therefore, you have to show the interviewer two things: 113 | 114 | 1. Your solution is good enough in terms of speed and memory 115 | 2. You are capable of evaluating the time and memory complexity of various algorithmic solutions 116 | 117 | ## 4. Code 118 | After you've identified the problem's constraints, discussed a few ideas, 119 | analyzed their complexities, and found one that both you and your interviewer 120 | think is worth being implemented, you go into writing the code. 121 | 122 | We see many people who jump straight into coding, ignoring all previous steps. 123 | This is bad in real life, and it’s bad when done at your interview. Never, ever jump straight into coding before having thought about and discussed constraints, ideas and complexities with your interviewer. 124 | 125 | * Think before you code: Especially if you're coding on a sheet of paper (where there is 126 | no undo), if you are not careful everything could become very messy very quickly 127 | * Coding outside of an IDE does not give you permission to stop abiding by good code style 128 | * It's even more important to decompose your code into small logical pieces 129 | * Read your code multiple times before you claim it's ready. 130 | 131 | ## 5. Tests 132 | Finally, you move on to writing test cases and testing your code. 133 | 134 | Showing that you know and care about testing is a great advantage. It demonstrates that you fundamentally understand the important part testing plays in the software development process. It gives the interviewer confidence that once hired, you’re not going to start writing buggy code and ship it to production, and instead you’re going to write unit tests, review your code, work through example scenarios, etc. 135 | 136 | ### Sample tests vs Extensive tests 137 | We call the first kind *sample* tests: these are small examples that you can feed to your code, and run through it line by line to make sure it works. You may want to do this once your code is written, or your interviewer may ask you to do it. The keyword here is "small": not "simple" or "trivial". 138 | 139 | Alternatively, your interviewer may ask you to do more *extensive* testing, where they ask you to come up with some good test cases for your solution. Typically, you will not be asked to run them step-by-step. It is more of a mental test design exercise to demonstrate whether you're skilled at unit testing your code. 140 | 141 | ### Extensive Testing 142 | * Edge cases 143 | * Cases when there is no solution 144 | * Non-trivial functional tests 145 | * Randomized tests 146 | * Load testing 147 | 148 | ### Sample Testing 149 | Sample tests are small tests that you run your code on at the interview to make sure 150 | it's solid. This combination (non-trivial functional + edge + no solution) tends to be the most effective. 151 | For the amount of time it takes to design the tests and to run them on your code on a sheet of paper, 152 | it gives you the highest certainty in your code. 153 | -------------------------------------------------------------------------------- /doc/binary_heap.md: -------------------------------------------------------------------------------- 1 | # Heap 2 | Heap is typically built on top of a binary tree. It is a *complete tree* which means it completes each level before it moves onto to build the next level. A *full tree* is a tree with every level completely filled. 3 | 4 | Heap invariant: any node's parent must be less than or equal to the node itself (minHeap). If it's a maxHeap, then any node's parent must be greater than or equal to the node itself. 5 | 6 | Primary APIs 7 | * #find_mid or #find_max 8 | * #insert 9 | * #extract 10 | * #count 11 | 12 | ## Implementation: MinHeap 13 | ``` ruby 14 | class BinaryMinHeap 15 | def initialize(&prc) 16 | @store = [] 17 | end 18 | 19 | def count 20 | @store.length 21 | end 22 | 23 | def extract 24 | temp = @store[0] 25 | @store[0] = @store[-1] 26 | @store[-1] = temp 27 | extracted_val = @store.pop 28 | BinaryMinHeap.heapify_down(@store, 0) 29 | extracted_val 30 | end 31 | 32 | def peek 33 | end 34 | 35 | def push(val) 36 | @store.push(val) 37 | BinaryMinHeap.heapify_up(@store, @store.length - 1) 38 | end 39 | 40 | protected 41 | attr_accessor :prc, :store 42 | 43 | public 44 | def self.child_indices(len, parent_index) 45 | children = [] 46 | first_child = parent_index*2 + 1 47 | children << first_child if first_child < len 48 | second_child = parent_index*2 + 2 49 | children << second_child if second_child < len 50 | children 51 | end 52 | 53 | def self.parent_index(child_index) 54 | raise "root has no parent" if child_index == 0 55 | (child_index - 1)/2x 56 | end 57 | 58 | def self.heapify_down(array, parent_idx, len = array.length, &prc) 59 | unless prc 60 | prc = Proc.new {|parent, child| parent <=> child} 61 | end 62 | 63 | children = BinaryMinHeap.child_indices(len, parent_idx) 64 | return array if children.length == 0 65 | 66 | case children.length 67 | when 0 68 | return array 69 | when 1 70 | if prc.call(array[parent_idx], array[children[0]]) == 1 71 | temp = array[parent_idx] 72 | array[parent_idx] = array[children[0]] 73 | array[children[0]] = temp 74 | end 75 | when 2 76 | if prc.call(array[parent_idx],array[children[0]]) == 1 || prc.call(array[parent_idx],array[children[1]]) == 1 77 | if prc.call(array[children[0]],array[children[1]]) == -1 78 | temp = array[parent_idx] 79 | array[parent_idx] = array[children[0]] 80 | array[children[0]] = temp 81 | else 82 | temp = array[parent_idx] 83 | array[parent_idx] = array[children[1]] 84 | array[children[1]] = temp 85 | end 86 | end 87 | end 88 | BinaryMinHeap.heapify_down(array, children[0], &prc) 89 | BinaryMinHeap.heapify_down(array, children[1], &prc) if children.length == 2 90 | end 91 | 92 | def self.heapify_up(array, child_idx, len = array.length, &prc) 93 | unless prc 94 | prc = Proc.new {|parent, child| parent <=> child} 95 | end 96 | # base case 97 | return array if child_idx == 0; 98 | 99 | parent_idx = BinaryMinHeap.parent_index(child_idx) 100 | if prc.call(array[parent_idx], array[child_idx]) == 1 101 | temp = array[parent_idx] 102 | array[parent_idx] = array[child_idx] 103 | array[child_idx] = temp 104 | end 105 | child_idx = parent_idx 106 | BinaryMinHeap.heapify_up(array, child_idx, &prc) 107 | end 108 | end 109 | ``` 110 | 111 | ## Insertion & Extraction 112 | Insertion and extraction requires O(log(n)) time because the heap needs to 113 | restructure itself to maintain heap invariant. 114 | 115 | For example, 116 | ![heapify_up][heapify_up] 117 | [heapify_up]: ../img/heapify_up.png 118 | -------------------------------------------------------------------------------- /doc/binary_search.md: -------------------------------------------------------------------------------- 1 | # Binary Search 2 | 3 | ## Implementation 4 | #### Ruby 5 | ``` ruby 6 | def binary_search(sorted_arr, target) 7 | return nil if sorted_arr.empty? 8 | 9 | probe_idx = sorted_arr.length/2 10 | case target <=> sorted_arr[probe_idx] 11 | when -1 12 | # search in left 13 | binary_search(sorted_arr.take(probe_idx), target) 14 | when 0 15 | # target is found 16 | probe_idx 17 | when 1 18 | # search in right 19 | sub_answer = binary_search(sorted_arr.drop(probe_idx + 1), target) 20 | if sub_answer.nil? 21 | nil 22 | else 23 | # make sure to put in offset for the index 24 | probe_idx + 1 + sub_answer 25 | end 26 | end 27 | end 28 | ``` 29 | 30 | #### JavaScript 31 | ``` javascript 32 | function binarySearch(sortedArray, target) { 33 | if (sortedArray.length === 0) { 34 | return null; 35 | } else { 36 | let probeIndex = Math.floor(sortedArray.length/2); 37 | if (target < sortedArray[probeIndex]) { 38 | let left = sortedArray.slice(0, probeIndex); 39 | return binarySearch(left, target); 40 | } else if (target > sortedArray[probeIndex]) { 41 | let right = sortedArray.slice(probeIndex + 1); 42 | let subAnswer = binarySearch(right, target); 43 | if (subAnswer) { 44 | return probeIndex + subAnswer + 1; 45 | } else { 46 | return null; 47 | } 48 | } else { 49 | return probeIndex; 50 | } 51 | } 52 | } 53 | ``` 54 | ## Interview Problems 55 | `three_sum` - Write a function to determine whether any 3 elements in an 56 | array sum to zero or any target. Brute force will take O(n^3) time, a better 57 | solution will take O(n^2 log(n)) time. An even better solution will take O(n^2) time 58 | with O(n) space. 59 | -------------------------------------------------------------------------------- /doc/binary_search_tree.md: -------------------------------------------------------------------------------- 1 | # Binary Search tree 2 | Primary API: 3 | * #insert - O(log n) 4 | * #find - O(log n) 5 | * #delete - O(log n) 6 | * #max 7 | * #min 8 | * #height 9 | * #inorder 10 | * #postorder 11 | * #pre-order 12 | 13 | ## Implementation 14 | ``` ruby 15 | class BSTNode 16 | attr_accessor :left, :right 17 | attr_reader :value 18 | 19 | def initialize(value) 20 | @value = value 21 | @left = nil 22 | @right = nil 23 | end 24 | end 25 | 26 | class BinarySearchTree 27 | def initialize 28 | @root_node = nil 29 | end 30 | 31 | def insert(value) 32 | if @root_node 33 | BinarySearchTree.insert!(@root_node, value) 34 | else 35 | @root_node = BSTNode.new(value) 36 | end 37 | end 38 | 39 | def find(value) 40 | BinarySearchTree.find!(@root_node, value) 41 | end 42 | 43 | def inorder 44 | BinarySearchTree.inorder!(@root_node) 45 | end 46 | 47 | def postorder 48 | BinarySearchTree.postorder!(@root_node) 49 | end 50 | 51 | def preorder 52 | BinarySearchTree.preorder!(@root_node) 53 | end 54 | 55 | def height 56 | BinarySearchTree.height!(@root_node) 57 | end 58 | 59 | def min 60 | BinarySearchTree.min(@root_node) 61 | end 62 | 63 | def max 64 | BinarySearchTree.max(@root_node) 65 | end 66 | 67 | def delete(value) 68 | BinarySearchTree.delete!(@root_node, value) 69 | end 70 | 71 | def self.insert!(node, value) 72 | # Base case is when node.left or node.right is nil 73 | if node 74 | if value <= node.value 75 | node.left = BinarySearchTree.insert!(node.left, value) 76 | elsif value > node.value 77 | node.right = BinarySearchTree.insert!(node.right, value) 78 | end 79 | node 80 | else 81 | BSTNode.new(value) 82 | end 83 | end 84 | 85 | def self.max(node) 86 | if node 87 | return node if node.right.nil? 88 | BinarySearchTree.max(node.right) 89 | else 90 | nil 91 | end 92 | end 93 | 94 | def self.min(node) 95 | if node 96 | return node if node.left.nil? 97 | BinarySearchTree.min(node.left) 98 | else 99 | nil 100 | end 101 | end 102 | 103 | def self.find!(node, value) 104 | if node 105 | if value < node.value 106 | BinarySearchTree.find!(node.left, value) 107 | elsif node.value < value 108 | BinarySearchTree.find!(node.right, value) 109 | else 110 | node 111 | end 112 | else 113 | nil 114 | end 115 | end 116 | 117 | def self.inorder!(node) 118 | return [] unless node 119 | arr = [] 120 | arr += BinarySearchTree.inorder!(node.left) 121 | arr << node.value 122 | arr += BinarySearchTree.inorder!(node.right) 123 | arr 124 | end 125 | 126 | def self.preorder!(node) 127 | return [] unless node 128 | arr = [node.value] 129 | arr += BinarySearchTree.preorder!(node.left) 130 | arr += BinarySearchTree.preorder!(node.right) 131 | arr 132 | end 133 | 134 | def self.postorder!(node) 135 | return [] unless node 136 | arr = [] 137 | arr += BinarySearchTree.postorder!(node.left) 138 | arr += BinarySearchTree.postorder!(node.right) 139 | arr << node.value 140 | arr 141 | end 142 | 143 | def self.height!(node) 144 | if node 145 | return 0 if node.left.nil? && node.right.nil? 146 | 147 | depth = [] 148 | if node.left 149 | depth << 1 + BinarySearchTree.height!(node.left) 150 | end 151 | if node.right 152 | depth << 1 + BinarySearchTree.height!(node.right) 153 | end 154 | depth.max 155 | else 156 | -1 157 | end 158 | end 159 | 160 | def self.delete_min!(node) 161 | return nil if node.nil? 162 | if node.left 163 | node.left = BinarySearchTree.delete_min!(node.left) 164 | node 165 | else 166 | node.right 167 | end 168 | end 169 | 170 | # Known as Hubbard Deletion 171 | # Either delete and replace with the minimum of the right subtree 172 | # or with the maximum of the left subtree 173 | def self.delete!(node, value) 174 | return nil if node.nil? 175 | 176 | if value < node.value 177 | node.left = BinarySearchTree.delete!(node.left, value) 178 | elsif node.value < value 179 | node.right = BinarySearchTree.delete!(node.right, value) 180 | else 181 | return node.left if node.right.nil? 182 | return node.right if node.left.nil? 183 | # What is it trying to do? 184 | t = node 185 | node = t.right.min 186 | node.right = BinarySearchTree.delete_min!(t.right) 187 | node.left = t.left 188 | end 189 | end 190 | end 191 | ``` 192 | ## Interview Problems 193 | __Symmetric Tree__: Write a function to check whether a binary search tree is mirrored 194 | ``` ruby 195 | def isMirrored(left, right) 196 | return (left == nil && right == nil) if left == nil || right == nil 197 | left.value == right.value && isMirrored(left.left, right.right) && isMirrored(left.right, right.left) 198 | end 199 | ``` 200 | 201 | __Minimal Tree__: Given a sorted (increasing order) array with unique 202 | integer elements, write an algorithm to create a binary search tree with 203 | minimal height. 204 | ``` ruby 205 | def minimal_insert(bstree, arr) 206 | if arr.length == 1 207 | bstree.insert(arr[0]) 208 | elsif arr.length == 0 209 | # do nothing 210 | else 211 | mid_idx = arr.length/2 212 | bstree.insert(arr[mid_idx]) 213 | # Recurse on left sub-array 214 | minimal_insert(bstree, arr.take(mid_idx)) 215 | # Recurse on right sub-array 216 | minimal_insert(bstree, arr.drop(mid_idx + 1)) 217 | end 218 | end 219 | ``` 220 | 221 | __List of Depths__: Given a binary tree, design an algorithm which creates a 222 | linked list of all the nodes at each depth(e.g. if you have a tree with depth D, 223 | you will have D linked lists) 224 | ``` ruby 225 | def depth_lists(root) 226 | # Begin with inserting root into the first LinkedList 227 | linkedlist_arr = [] 228 | current_list = LinkedList.new 229 | current_list.insert(root.value, root) unless root.nil? 230 | until current_list.empty? 231 | # Push the previous level into the array 232 | linkedlist_arr << current_list 233 | # The links from previous level are the parent nodes 234 | parents = current_list 235 | # Re-assign current list to an en empty list 236 | current_list = LinkedList.new 237 | # Go through each parent and insert their children into current list 238 | parents.each do |parent| 239 | parentNode = parent.val 240 | current_list.insert(parentNode.left.value, parentNode.left) unless parentNode.left.nil? 241 | current_list.insert(parentNode.right.value, parentNode.right) unless parentNode.right.nil? 242 | end 243 | # Go back to top 244 | end 245 | linkedlist_arr 246 | end 247 | ``` 248 | 249 | __Check Balanced__: Implement a function to check if a binary tree is balanced. 250 | For the purposes of this question, a balanced tree is defined to be a tree 251 | such that the heights of the two subtrees of any node never differ by more 252 | than one. 253 | 254 | ``` ruby 255 | # O(n log n) 256 | def getHeight(root_node) 257 | return -1 if root_node.nil? 258 | [getHeight(root_node.left), getHeight(root_node.right)].max + 1 259 | end 260 | 261 | def is_balanced?(root_node) 262 | return true if root_node.nil? 263 | height_diff = getHeight(root.left) - getHeight(root.right) 264 | if height_diff.abs > 1 265 | false 266 | else 267 | is_balanced?(root.left) && is_balanced?(root.right) 268 | end 269 | end 270 | ``` 271 | ``` ruby 272 | # O(n) with space O(Height) 273 | def check_height(root) 274 | return -1 if root.nil? 275 | left_height = check_height(root.left) 276 | return false unless left_height 277 | right_height = check_height(root.right) 278 | return false unless right_height 279 | 280 | height_diff = left_height - right_height 281 | if height_diff > 1 282 | false 283 | else 284 | [left_height, right_height].max + 1 285 | end 286 | end 287 | 288 | def is_balanced?(root_node) 289 | !!check_height(root_node) 290 | end 291 | ``` 292 | 293 | __Validate BST__: Implement a function to check if a binary tree is binary 294 | search tree 295 | 296 | If the tree does not have duplicate value, do an in-order traversal and 297 | see if the elements are sorted, if not, it's not a valid BST. But let's say 298 | we need a solution that allows duplicates. Then this is the approach: 299 | ``` javascript 300 | /* 301 | This approach is using the fact that the maximum of left subtree must be 302 | less than or equal to the root node and the minimum of right subtree must be 303 | greater than the root node. 304 | */ 305 | function validateBST(root) { 306 | if (root.left && root.right) { 307 | return (BSTree.max(root.left).value <= root.value && 308 | BSTree.max(root.right).value > root.value) && 309 | (validateBST(root.left) && validateBST(root.right)); 310 | } else if (root.left) { 311 | return (BSTree.max(root.left).value <= root.value) && 312 | validateBST(root.left); 313 | } else if (root.right) { 314 | return (BSTree.max(root.right).value > root.value) && 315 | validateBST(root.right); 316 | } else { 317 | return true; 318 | } 319 | } 320 | ``` 321 | 322 | __Successor__: Write an algorithm to find the "next" node (i.e. in-order 323 | successor) of a given node in a binary search tree. You may assume that 324 | each node has a link to its parent. 325 | 326 | This problem is easy if we choose the root of the tree or any node that 327 | has a right subtree because the next node has to be the left-most node of 328 | the right subtree. What if a node does not have a right subtree? 329 | ``` ruby 330 | # If the maximum of the left sub-tree of the root is chosen, then it has no 331 | # right subtree. It has to traverse upward to find the successor 332 | def successor(node) 333 | if node.right 334 | BinarySearchTree.left_most(node.right) 335 | else 336 | current_node = node 337 | parent_node = current_node.parent 338 | while !parent_node.nil? && parent_node.right == current_node 339 | # Traverse upward until current_node finds itself to be the left child 340 | current_node = parent_node 341 | parent_node = current_node.parent 342 | end 343 | current_node.parent 344 | end 345 | end 346 | 347 | def BinarySearchTree.left_most(node) 348 | return nil if node.nil? 349 | until node.left.nil? 350 | node = node.left 351 | end 352 | node 353 | end 354 | ``` 355 | -------------------------------------------------------------------------------- /doc/bit_manipulation.md: -------------------------------------------------------------------------------- 1 | # Bit Manipulation 2 | 3 | ## Signed Binary Number: Two's complement 4 | In two's complement, there is only one zero, represented as 0000 0000. 5 | Negating a number is done by inverting all the bits and then adding one 6 | to that result. With 8 bit, the most positive number it can represent 7 | is 127 and the most negative number is -128. 8 | 9 | | Binary Value | Two's Complement | Unsigned | 10 | | ------------ | ---------------- | -------- | 11 | | 0000 0000 | 0 | 0 | 12 | | 0000 0001 | 1 | 1 | 13 | | 0000 0010 | 2 | 2 | 14 | | 0000 0011 | 3 | 3 | 15 | | ... | 4... | 4... | 16 | | 0111 1111 | 127 | 127 | 17 | | 1000 0000 | -128 | 128 | 18 | | 1000 0001 | -127 | 129 | 19 | | 1000 0010 | -126 | 130 | 20 | | 1111 1110 | -2 | 254 | 21 | | 1111 1111 | -1 | 255 | 22 | 23 | ## Bitwise Operators in Ruby 24 | * AND - Set the result's bit to 1 if it is 1 in both input values 25 | * OR - Set the result's bit to 1 if it is 1 in either input value 26 | * XOR - Set the result's bit to 1 if it is 1 in either input value but not both 27 | * Inverse - Set the result's bit to 0 if the input is 1 and vice versa 28 | * Left Shift - Move the input bits left by a specified number of places 29 | * Right Shift - Move the input bits right by a specified number of places 30 | 31 | | Operators | Symbol | Inputs | Outputs | 32 | | ----------- | ------ | ------------ | --------- | 33 | | AND | & | 1010 & 0010 | 0010 | 34 | | OR | | 1010 OR 1111 | 1111 | 35 | | XOR | ^ | 1010 ^ 1111 | 0101 | 36 | | Inverse | ~ | ~1010 | 0101 | 37 | | Left Shift | << | 0101 << 2 | 0001 0100 | 38 | | Right Shift | >> | 1100 >> 2 | 0011 | 39 | 40 | ## GameBoy 41 | Gameboy CPU has 10 registers, A B C D E F G H L SP PC 42 | Each of them can hold 8 bit numbers 43 | SP and PC can hold 16 bit numbers 44 | 45 | Work RAM holds 8 KB, 8000 cells with 8 bits 46 | CPU has the ability to perform instructions 47 | 48 | `LD A,B` - load function, from second operator and copy it to the first 49 | Take value from B register and copy it to A register 50 | 51 | `ADD A, 27` - add function, add value from 27 to A 52 | 53 | `JP NZ, 8000H` - jump instruction, NZ - if not zero, jump to 8000 54 | 55 | `HALT` - pauses the CPU until a new screen is refreshed 56 | 57 | ### Assembly language 58 | ``` 59 | LD A, 0 - Put 0 into register A 60 | LD (8000H), A - Copy the content of A into memory location 8000 hexidecimal 61 | game goes on ... 62 | LD A, (8000H) - Copy the value from 8000H back to A to perform computation 63 | INC A - Increment by one 64 | LD (8000H), A - Put it back into memory 65 | SUB 100 - Subtract 100 from A 66 | JP C, AFTER - Jump to the position labeled AFTER when the previous operation went below zero 67 | LD A, 0 - Set A back to zero 68 | LD (8000H), A - Copy it back to the memory location 69 | LD A, (8001H) - Load the memory location of life to A 70 | INC A - Increment A 71 | LD (8001H), A - Copy it back to the memory location of life 72 | AFTER: 73 | ``` 74 | -------------------------------------------------------------------------------- /doc/breadth_first_search.md: -------------------------------------------------------------------------------- 1 | # Breadth First Search (Tree & Graph) 2 | 3 | ## Implementation 4 | Breadth first search requires a queue. The first step is to insert the root 5 | into the queue and start the while loop. While the queue is not empty, pop 6 | the element off (i.e. shift) from beginning of the queue and examine its value. 7 | Then push its children/neighbors into the queue. 8 | ``` ruby 9 | def bfs(root, target) 10 | queue = [root] 11 | until queue.empty? 12 | probe = queue.shift 13 | return probe if probe.value == target 14 | probe.children.each do |child| 15 | queue << child 16 | end 17 | end 18 | nil 19 | end 20 | ``` 21 | 22 | ## Implementation (Graph) 23 | The graph implementation requires marking the node as visited to prevent 24 | infinite recursion 25 | #### Ruby 26 | ``` ruby 27 | class Vertex 28 | attr_accessor :value, :in_edges, :out_edges 29 | def initialize(value) 30 | @value, @in_edges, @out_edges = value, [], [] 31 | @visited = false 32 | end 33 | 34 | def visited? 35 | @visited 36 | end 37 | 38 | def visit! 39 | @visited = true 40 | end 41 | end 42 | 43 | class Edge 44 | attr_accessor :to_vertex, :from_vertex, :cost 45 | def initialize(from_vertex, to_vertex, cost = 1) 46 | self.from_vertex = from_vertex 47 | self.to_vertex = to_vertex 48 | self.cost = cost 49 | 50 | to_vertex.in_edges << self 51 | from_vertex.out_edges << self 52 | end 53 | 54 | def destroy! 55 | self.to_vertex.out_edges.delete(self) 56 | self.to_vertex = nil 57 | self.from_vertex.in_edges.delete(self) 58 | self.from_vertex = nil 59 | end 60 | end 61 | 62 | def bfs(node, target) 63 | queue = [node] 64 | until queue.empty? 65 | probe = queue.shift 66 | probe.visit! 67 | return probe if probe.value == target 68 | probe.out_edges.each do |edge| 69 | queue << edge.to_vertex unless edge.to_vertex.visited? 70 | end 71 | end 72 | nil 73 | end 74 | ``` 75 | 76 | #### JavaScript 77 | ``` javascript 78 | //Graphs 79 | class Vertex { 80 | constructor(value) { 81 | this.value = value; 82 | this.inEdges = []; 83 | this.outEdges = []; 84 | this.visited = false; 85 | } 86 | 87 | visit() { 88 | this.visited = true; 89 | } 90 | 91 | deleteOutEdge(outEdge) { 92 | for (let i = 0; i < this.outEdges.length; i++) { 93 | if (this.outEdges[i] === outEdge) { 94 | this.outEdges.splice(i, 1); 95 | break; 96 | } 97 | } 98 | } 99 | 100 | deleteInEdge(inEdge) { 101 | for (let i = 0; i < this.inEdges.length; i++) { 102 | if (this.inEdges[i] === inEdge) { 103 | this.inEdges.splice(i, 1); 104 | break; 105 | } 106 | } 107 | } 108 | } 109 | 110 | class Edge { 111 | constructor(fromVertex, toVertex, cost) { 112 | this.fromVertex = fromVertex; 113 | this.toVertex = toVertex; 114 | this.cost = cost || 1; 115 | fromVertex.outEdges.push(this); 116 | toVertex.inEdges.push(this); 117 | } 118 | 119 | destroy() { 120 | this.toVertex.deleteInEdge(this); 121 | this.fromVertex.deleteOutEdge(this); 122 | this.toVertex = undefined; 123 | this.fromVertex = undefined; 124 | } 125 | } 126 | 127 | function breadthFirstSearch(vertex, target) { 128 | let queue = [vertex]; 129 | while (queue.length > 0) { 130 | let probeVertex = queue.shift(); 131 | probeVertex.visit(); 132 | if (probeVertex.value === target) { 133 | return probeVertex; 134 | } else { 135 | probeVertex.outEdges.forEach((outEdge) => { 136 | let neighborVertex = outEdge.toVertex; 137 | if (!neighborVertex.visit()) { 138 | queue.push(neighborVertex); 139 | } 140 | }); 141 | } 142 | } 143 | return null; 144 | } 145 | ``` 146 | -------------------------------------------------------------------------------- /doc/depth_first_search.md: -------------------------------------------------------------------------------- 1 | # Depth First Search (Tree & Graph) 2 | 3 | ## Implementation (Tree) 4 | This is the tree version. The search begins at the root and then traverse 5 | down to its children. When leaf node is reached, there is no children, the 6 | recursive step will terminate. It will return early once target is found. 7 | ``` ruby 8 | def dfs(root, target) 9 | return nil if root.nil? 10 | return root if root == target 11 | root.children.each do |child| 12 | search_result = dfs(child, target) 13 | return search_result unless search_result.nil? 14 | end 15 | nil 16 | end 17 | ``` 18 | 19 | ## Implementation (Graph) 20 | This is the graph version, which is just a slight modification of the tree 21 | version 22 | #### Ruby 23 | ``` ruby 24 | class Vertex 25 | attr_accessor :value, :in_edges, :out_edges 26 | def initialize(value) 27 | @value, @in_edges, @out_edges = value, [], [] 28 | @visited = false 29 | end 30 | 31 | def visited? 32 | @visited 33 | end 34 | 35 | def visit! 36 | @visited = true 37 | end 38 | end 39 | 40 | class Edge 41 | attr_accessor :to_vertex, :from_vertex, :cost 42 | def initialize(from_vertex, to_vertex, cost = 1) 43 | self.from_vertex = from_vertex 44 | self.to_vertex = to_vertex 45 | self.cost = cost 46 | 47 | to_vertex.in_edges << self 48 | from_vertex.out_edges << self 49 | end 50 | 51 | def destroy! 52 | self.to_vertex.out_edges.delete(self) 53 | self.to_vertex = nil 54 | self.from_vertex.in_edges.delete(self) 55 | self.from_vertex = nil 56 | end 57 | end 58 | 59 | def dfs(node, target) 60 | return nil if node.nil? || node.visited? 61 | node.visit! 62 | return node if node.value == target 63 | 64 | node.out_edges.each do |out_edge| 65 | search_result = dfs(out_edge.to_vertex, target) 66 | return search_result unless search_result.nil? 67 | end 68 | 69 | nil 70 | end 71 | ``` 72 | 73 | #### JavaScript 74 | ``` javascript 75 | //Graphs using ES6 Syntax 76 | class Vertex { 77 | constructor(value) { 78 | this.value = value; 79 | this.inEdges = []; 80 | this.outEdges = []; 81 | this.visited = false; 82 | } 83 | 84 | visit() { 85 | this.visited = true; 86 | } 87 | 88 | deleteOutEdge(outEdge) { 89 | for (let i = 0; i < this.outEdges.length; i++) { 90 | if (this.outEdges[i] === outEdge) { 91 | this.outEdges.splice(i, 1); 92 | break; 93 | } 94 | } 95 | } 96 | 97 | deleteInEdge(inEdge) { 98 | for (let i = 0; i < this.inEdges.length; i++) { 99 | if (this.inEdges[i] === inEdge) { 100 | this.inEdges.splice(i, 1); 101 | break; 102 | } 103 | } 104 | } 105 | } 106 | 107 | class Edge { 108 | constructor(fromVertex, toVertex, cost) { 109 | this.fromVertex = fromVertex; 110 | this.toVertex = toVertex; 111 | this.cost = cost || 1; 112 | fromVertex.outEdges.push(this); 113 | toVertex.inEdges.push(this); 114 | } 115 | 116 | destroy() { 117 | this.toVertex.deleteInEdge(this); 118 | this.fromVertex.deleteOutEdge(this); 119 | this.toVertex = undefined; 120 | this.fromVertex = undefined; 121 | } 122 | } 123 | 124 | function depthFirstSearch(vertex, target) { 125 | if (vertex.value === target) { 126 | return vertex; 127 | } else { 128 | for (let i = 0; i < vertex.outEdges.length; i++) { 129 | let neighborVertex = vertex.outEdges[i].toVertex; 130 | let searchResult = depthFirstSearch(neighborVertex, target); 131 | if (searchResult) { 132 | return searchResult; 133 | } 134 | } 135 | } 136 | return null; 137 | } 138 | ``` 139 | #### Iterative DFS 140 | There are times when you need an iterative version of depth first search. For example, 141 | when you try to traverse two tree simultaneously, it's wise to use two stacks 142 | and fire DFS on them simultaneously. 143 | 144 | ``` javascript 145 | function iterativeDFS(root, target) { 146 | let stack = [root]; 147 | while (stack.length > 0) { 148 | let currentNode = stack.pop(); 149 | if (currentNode.value === target) { 150 | return currentNode; 151 | } else { 152 | let children = currentNode.children; 153 | for (let i = children.length - 1; i >= 0; i--) { 154 | stack.push(children[i]); 155 | } 156 | } 157 | } 158 | return null; 159 | } 160 | ``` 161 | It's similar to breadth first search, instead of using queue, it uses a stack. 162 | But if you want to search from left to right, that is, left most bottom child first, then 163 | when you push the child into the stack, pushes the right most child first. 164 | -------------------------------------------------------------------------------- /doc/dijkstra.md: -------------------------------------------------------------------------------- 1 | # Dijkstra's Algorithm 2 | Dijkstra's algorithm is an algorithm for finding minimum distance between any 3 | two vertices on an directed weighted graph. The algorithm starts with initializing 4 | two data variable, a `considered_paths`/`locked_in_paths` that holds the minimum 5 | cost to each node from a source, a `frontiers` to hold nodes that are going 6 | to be considered/checked. 7 | 8 | The `frontiers` is usually implemented with a priority queue because through 9 | each iteration, we are only looking at node that has the lowest cost to reach 10 | to. An extract from a priority queue implemented with MinHeap will serve this 11 | purpose well. 12 | 13 | We start by putting source into the frontiers, and iterate while the frontier 14 | queue is not empty. Every time we extract node from the queue, we check its neighbors 15 | to update their path cost and put them in queue. Once finished checking, the node 16 | will go into the `considered_paths`/`locked_in_paths` and indicate that 17 | it's been visited. Once all nodes have been visited, the computation is finished. 18 | Return the `locked_in_paths`. 19 | 20 | ## Implementation 21 | This is the algorithm, using `frontiers` as a queue and extract elements 22 | in every round of iteration. This implementation did not use __PriorityQueue__, 23 | it used hash map for frontiers for the sake of simplicity. 24 | ``` ruby 25 | def dijkstra(source) 26 | considered_paths = {} 27 | frontiers = { 28 | source => {cost: 0, last_edge: nil} 29 | } 30 | until frontiers.empty? 31 | vertex = select_frontier(frontiers) 32 | considered_paths[vertex] = frontiers[vertex] 33 | frontiers.delete(vertex) 34 | update_frontiers(vertex, considered_paths, frontiers) 35 | end 36 | considered_paths 37 | end 38 | ``` 39 | For the lack of __PriorityQueue__, one must iterate through the hash through 40 | enumerable method and find the vertex with minimum cost. This is O(|V|) time. 41 | ``` ruby 42 | def select_frontier(frontiers) 43 | vertex, data = frontiers.min_by do |vertex, data| 44 | data[:cost] 45 | end 46 | vertex 47 | end 48 | ``` 49 | This will look at a vertex's out going edges and find the reachable vertices. 50 | `extended_path_cost` is the cost to reach to vertex plus the cost of the edge 51 | to reach a neighbor. For example, we are looking at vertex A, and vertex B is 52 | A's neighbor. If the cost to reach B from A is lower than any other path, the 53 | path cost will be updated and B will go into the queue. But if B is already 54 | in the queue, and the cost to reach B is lower through other vertices, do nothing 55 | and move on. 56 | ``` ruby 57 | def update_frontiers(vertex, considered_paths, frontiers) 58 | path_to_vertex_cost = considered_paths[vertex][:cost] 59 | vertex.out_edges.each do |edge| 60 | neighbor = edge.to_vertex 61 | next if considered_paths.has_key?(neighbor) 62 | extended_path_cost = path_to_vertex_cost + edge.cost 63 | if frontiers.has_key?(neighbor) && frontiers[neighbor][:cost] <= extended_path_cost 64 | next 65 | else 66 | frontiers[neighbor] = {cost: extended_path_cost, last_edge: edge} 67 | end 68 | end 69 | end 70 | ``` 71 | 72 | ## Example 73 | Notes 74 | * __yellow__: frontiers 75 | * __gray__: locked_in nodes 76 | 77 | ![example][dijkstra] 78 | 79 | [dijkstra]: ../img/dijkstra.png 80 | 81 | Here are the steps 82 | 1. `frontiers` => {A: 0} 83 | 2. `frontiers` => {B: 2, C: 5}, `locked` => {A: 0} 84 | * Pick out `B` from frontiers and check its neighbors 85 | 3. `frontiers` => {C: 5, D: 7, E: 12}, `locked` => {A: 0, B: 2} 86 | * Pick out `C` from frontiers and check its neighbors 87 | 4. `frontiers` => {D: 7, E: 12}, `locked` => {A: 0, B: 2, C: 5} 88 | * Pick out `D` from frontiers and check its neighbors 89 | * Update `E` because it is cheaper to reach E from A -> B -> D -> E than 90 | from A -> B -> E 91 | 5. `frontiers` => {E: 10}, `locked` => {A: 0, B: 2, C: 5, D: 7} 92 | * Take `E` out and there is no neighbors, iteration ends 93 | 6. Final result {A: 0, B: 2, C: 5, D: 7, E: 10} 94 | -------------------------------------------------------------------------------- /doc/dynamic_array.md: -------------------------------------------------------------------------------- 1 | # Dynamic Array (Ring Buffer) 2 | 3 | ## Implementation 4 | ``` ruby 5 | class DynamicArray 6 | attr_reader :length 7 | 8 | def initialize(size = 8) 9 | @store = StaticArray.new(size) 10 | @start_idx = 0 11 | @length = 0 12 | @capacity = size 13 | end 14 | 15 | # O(1) 16 | def [](index) 17 | if index >= length 18 | raise "index out of bounds" 19 | end 20 | @store[self.check_index(index)] 21 | end 22 | 23 | # O(1) 24 | def []=(index, value) 25 | @store[self.check_index(index)] = value 26 | end 27 | 28 | # O(1) 29 | def pop 30 | if @length == 0 31 | raise "index out of bounds" 32 | end 33 | @length -= 1 34 | @store[self.check_index(@length)] 35 | end 36 | 37 | # O(1) amortized; O(n) worst case. 38 | def push(val) 39 | if @length == @capacity 40 | self.resize! 41 | end 42 | @store[self.check_index(@length)] = val 43 | @length += 1 44 | end 45 | 46 | # O(1) 47 | def shift 48 | if @length == 0 49 | raise "index out of bounds" 50 | end 51 | val = @store[@start_idx] 52 | @start_idx = (@start_idx + 1) % @capacity 53 | @length -= 1 54 | val 55 | end 56 | 57 | # O(1) ammortized 58 | def unshift(val) 59 | if @length == @capacity 60 | self.resize! 61 | end 62 | @length += 1 63 | @start_idx = (@start_idx - 1) % @capacity 64 | @store[@start_idx] = val 65 | end 66 | 67 | protected 68 | attr_accessor :capacity, :start_idx, :store 69 | attr_writer :length 70 | 71 | def check_index(index) 72 | (@start_idx + index) % @capacity 73 | end 74 | 75 | def resize! 76 | new_store = StaticArray.new(2*@capacity) 77 | i = 0 78 | while i < @length 79 | new_store[i] = @store[(@start_idx + i) % capacity] 80 | i += 1 81 | end 82 | @capacity *= 2 83 | @start_idx = 0 84 | @store = new_store 85 | end 86 | 87 | end 88 | ``` 89 | ## Interview Problems 90 | `is_unique` - implement an algorithm to determine if a string has all unique 91 | characters. What if you cannot use additional data structure? 92 | ``` ruby 93 | # build a hash to count number of char appearance 94 | def is_unique(str) 95 | letter_count = Hash.new 96 | str.chars.each do |char| 97 | if letter_count[char] 98 | return false 99 | else 100 | letter_count[char] = 1 101 | end 102 | end 103 | true 104 | end 105 | 106 | # no hash 107 | def is_unique(str) 108 | str = str.chars.sort.join 109 | (1...str.length).each do |i| 110 | return false if str[i] == str[i - 1] 111 | end 112 | true 113 | end 114 | ``` 115 | 116 | `check_permutation` - given two strings, write a method to decide if one is 117 | a permutation of the other 118 | ``` ruby 119 | # O(n) time by creating a hash for both strings 120 | def check_permutation(str1, str2) 121 | str1_hash = Hash.new 122 | str1.chars.each do |char| 123 | if str1_hash[char] 124 | str1_hash[char] += 1 125 | else 126 | str1_hash[char] = 1 127 | end 128 | end 129 | 130 | str2_hash = Hash.new 131 | str2.chars.each do |char| 132 | if str2_hash[char] 133 | str2_hash[char] += 1 134 | else 135 | str2_hash[char] = 1 136 | end 137 | end 138 | 139 | str1_hash == str2_hash 140 | end 141 | ``` 142 | 143 | `rotate_matrix` - given an image represented by an NxN matrix, where each pixel 144 | in the image is 4 bytes, write a method to rotate the image by 90 degrees. 145 | Can do you this place? 146 | ``` ruby 147 | # first reverse each row array, and then do a transpose 148 | # the result is that the matrix is rotated 90 degree counter-clockwise 149 | def rotate_matrix(mat) 150 | rotated_mat = [] 151 | mat.each_index do |i| 152 | reversed_row = [] 153 | (mat[i].length - 1).downto(0) do |j| 154 | reversed_row << mat[i][j] 155 | end 156 | rotated_mat << reversed_row 157 | end 158 | 159 | (0...rotated_mat.length).each do |i| 160 | (i...rotated_mat[i].length).each do |j| 161 | rotated_mat[i][j], rotated_mat[j][i] = rotated_mat[j][i], rotated_mat[i][j] 162 | end 163 | end 164 | 165 | rotated_mat 166 | end 167 | ``` 168 | 169 | `is_palindrome` - check if a string is a palindrome by using recursion 170 | ``` ruby 171 | def is_palindrome?(str) 172 | return true if str.length <= 1 173 | if str[0] == str[str.length - 1] 174 | is_palindrome?(str[1...str.length - 1]) 175 | else 176 | return false 177 | end 178 | end 179 | ``` 180 | -------------------------------------------------------------------------------- /doc/finite_state_automata.md: -------------------------------------------------------------------------------- 1 | # Finite State Automata 2 | 3 | ## Naive 4 | The naive approach toward string matching is that, given a string with 5 | length n, and a pattern or substring with length m: 6 | 7 | Using an example, string = "ababc" and pattern = "abc" 8 | 9 | 1. Iterate through `ababc` and `abc` simultaneously, using index i for string 10 | and index j for pattern 11 | 2. Check `a`: string[0] == pattern[0], 12 | 3. Check `b`: string[1] == pattern[1], 13 | 4. Check `a`: string[2] != pattern[2], reset j to be 0 14 | 5. Check `a` again so string[2] == pattern[0] 15 | 6. Check `b`: string[3] == pattern[1] and so on... 16 | 17 | This naive algorithm will run O(n*m) time. 18 | 19 | ## Keep Track of States 20 | Ultimately, if we can avoid resetting the index j in the pattern string, we can optimize this algorithm to O(n + m). The trick is to keep track of states. 21 | 22 | Let's say we are given a pattern string `abc`. We will have four states, 23 | * State 0: No match or match empty string 24 | * State 1: Match `a` 25 | * State 2: Match `ab` 26 | * State 3: Match `abc` 27 | 28 | Now we construct a table, and we call this the transition table (it means how state should transition to another for a given input) 29 | 30 | | States \ Input | a | b | c | 31 | |------------------|---------|---------|---------| 32 | | State 0: `empty` | State 1 | State 0 | State 0 | 33 | | State 1: `a` | State 1 | State 2 | State 0 | 34 | | State 2: `ab` | State 1 | State 0 | State 3 | 35 | | State 3: `abc` | Completion 36 | 37 | ![finite_state_automata] 38 | [finite_state_automata]: ../img/finite_state_automata.png 39 | 40 | #### Interpretation 41 | At a given state, input is the character that you get from scanning through 42 | a string. Say we are at State 0, and next incoming character is `a` then we 43 | will proceed to State 1, else stay at State 0. 44 | 45 | ## Concrete Example 46 | Using the original string `ababc`, pattern `abc`, and the transition table 47 | we have constructed above. We will show how this finite state automata works. 48 | ``` 49 | Iteration #1 50 | input = 'a' 51 | state = 0 -> 1 52 | 53 | Iteration #2 54 | input = 'b' 55 | state = 1 -> 2 56 | 57 | Iteration #3 58 | input = 'a' 59 | state = 2 -> 1 60 | 61 | Iteration #4 62 | input = 'b' 63 | state = 1 -> 2 64 | 65 | Iteration #5 66 | input = 'c' 67 | state = 2 -> 3 68 | 69 | Once state 3 is reached, it means substring is found 70 | ``` 71 | ## Implementation 72 | ``` ruby 73 | def transition_table(pattern, radix) 74 | table = Hash.new 75 | (0..pattern.length).each do |i| 76 | table["state:#{i}"] = {pattern: pattern[0...i]} 77 | end 78 | 79 | (0...pattern.length).each do |i| 80 | radix.each do |char| 81 | str = table["state:#{i}"][:pattern] + char 82 | discard_num = 0 83 | (i+1).downto(0) do |j| 84 | if table["state:#{j}"][:pattern] == str.chars.drop(discard_num).join 85 | table["state:#{i}"][char] = "state:#{j}" 86 | next 87 | end 88 | discard_num += 1 89 | end 90 | end 91 | end 92 | 93 | table 94 | end 95 | 96 | def is_match(string, pattern, radix) 97 | table = transition_table(pattern, radix) 98 | num_of_states = table.keys.length - 1 99 | current_state = "state:0" 100 | string.chars.each do |char| 101 | current_state = table[current_state][char] 102 | return true if current_state == "state:#{num_of_states}" 103 | end 104 | false 105 | end 106 | ``` 107 | 108 | ## Challenge 109 | The challenge is in creating the transition table. Here's a basic rule of 110 | thumb. We begin with State 0 with an empty string `""`. For the example above, 111 | we have three radixes, they are `a`, `b`, and `c`. 112 | 113 | Start at state 0, we append these radixes to the string of current state then we ask 114 | ``` 115 | is "a" a match for the string of state 1? 116 | is "b" a match for the string of state 1? 117 | is "c" a match for ... 118 | ... 119 | ``` 120 | Since `a` is a match for the string of state 1, so we will say that, at 121 | state 0, given "a" as an input, we will transition to state 1. Since other 122 | inputs are not a match for state 1, they will transition back to state 0. 123 | 124 | At state 1, our string is `a`, we ask the same question again, 125 | ``` 126 | is "aa" a match for the string of state 2? No 127 | -> we drop the "a" in the front, 128 | is "a" a match for the string of state 1? Yes 129 | -> "aa" -> state 1 130 | 131 | is "ab" a match for the string of state 2? Yes 132 | -> "ab" -> state 2 133 | 134 | is "ac" a match for the string of state 2? No 135 | -> we drop the "a" in the front, 136 | is "c" a match for the string of state 1? No 137 | -> we drop the "c" in the front, 138 | is "" a match for the string of state 0? Yes 139 | -> "ac" -> state 0 140 | ``` 141 | 142 | At state 2, our string is now `ab`, we do the same thing as above. 143 | -------------------------------------------------------------------------------- /doc/graph.md: -------------------------------------------------------------------------------- 1 | # Graph 2 | There are two kinds of graphs, directed and undirected. Since we are specifying 3 | the directions of edges (i.e. inward and outward,) we are talking about 4 | directed graphs here. But we can always generalize the data structure to be 5 | undirected by making sure the edges go both ways. However, topological sort 6 | requires directed acyclic graphs. 7 | 8 | Two vertices are *connected* if there is a path between them. The edge typically 9 | carries information of the cost or distance of the path. 10 | 11 | ## Graph API 12 | * `#initialize(num_of_vertices)` 13 | * `#addEdge` - add connection between any two vertices 14 | * `#eachVertex` - takes in a block, iterate through all vertices 15 | 16 | ## Implementation 17 | ``` ruby 18 | class Vertex 19 | attr_accessor :in_edges, :out_edges, :value 20 | 21 | def initialize(value) 22 | @value = value 23 | @in_edges = [] 24 | @out_edges = [] 25 | end 26 | 27 | def delete_in_edge(edge) 28 | @in_edges.each_index do |idx| 29 | if @in_edges[idx] == edge 30 | @in_edges.delete_at(idx) 31 | end 32 | end 33 | end 34 | 35 | def delete_out_edge(edge) 36 | @out_edges.each_index do |idx| 37 | if @out_edges[idx] == edge 38 | @out_edges.delete_at(idx) 39 | end 40 | end 41 | end 42 | end 43 | 44 | class Edge 45 | attr_reader :source, :destination, :cost 46 | def initialize(source, destination, cost = 1) 47 | @source = source 48 | @destination = destination 49 | @cost = cost 50 | @source.out_edges << self 51 | @destination.in_edges << self 52 | end 53 | 54 | def destroy! 55 | @source.delete_out_edge(self) 56 | @destination.delete_in_edge(self) 57 | @source = nil 58 | @destination = nil 59 | end 60 | end 61 | ``` 62 | ## Graph Representation 63 | ### Adjacency List 64 | For each vertex i, store an array of adjacent vertices to it. We typically 65 | have an array of adjacency lists; each vertex gets a list (of neighbors.) We can also use a hash. 66 | 67 | Example: 68 | 69 | ![graph][graph] 70 | [graph]:../img/graph.png 71 | 72 | ``` 73 | i 74 | 0 - Calvin => [Matt, Steven] 75 | 1 - Matt => [Calvin, Steven] 76 | 2 - Steven => [Calvin, Matt, Loki] 77 | 3 - Loki => [Steven] 78 | ``` 79 | 80 | Example of Java implementation 81 | ``` java 82 | public class Graph 83 | { 84 | private final int V; 85 | private Bag[] adjList; 86 | 87 | public Graph(int V) 88 | { 89 | this.V = V; 90 | adjList = (Bag[]) new Bag[V]; 91 | for (int v = 0; v < V; v++) { 92 | adjList[v] = new Bag(); 93 | } 94 | } 95 | 96 | public void addEdge(int v, int w) 97 | { 98 | adjList[v].add(w); 99 | adjList[w].add(v); 100 | } 101 | } 102 | ``` 103 | 104 | ### Adjacency Matrix 105 | We can also use a matrix to represent the neighboring relationship above. 106 | The matrix should be symmetric for undirected graphs. 107 | ``` 108 | matrix = 109 | _ 0 1 2 3 110 | 0 * 1 1 0 111 | 1 1 * 1 0 112 | 2 1 1 * 1 113 | 3 0 0 1 * 114 | 115 | matrix[0][1] => connect from 0 to 1, in this case it's Calvin to Matt. 116 | If such connection exists, set matrix[0][1] = 1. 117 | 118 | matrix[0][0] is self connection, just give it any symbol; I used * 119 | ``` 120 | 121 | ### Density 122 | Density: 2|E|/(|V| |V - 1|) 123 | 124 | If density is close to 1, it's a dense graph. Use adjacency matrix for dense graphs and use adjacency list for sparse graphs. 125 | 126 | ## Connected Component 127 | Vertices v and w are connected if there is a path between them. 128 | 129 | ### Properties 130 | The relation "is connected to" is an equivalence relation: 131 | * Reflexive: *v* is connected to *v* 132 | * Symmetric: if *v* is connected to *w*, then *w* is connected to *v* 133 | * Transitive: if *v* connected to *w* and *w* connected to *x*, then *v* is 134 | connected to *x* 135 | 136 | 137 | A *connected component* is a maximal set of connected vertices 138 | 139 | The goal is to partition a set of vertices into connected component, we 140 | will achieve this in linear time by iterating through all the vertices 141 | contained in a graph. 142 | 143 | ### Approach 144 | 1. Initialize all vertices as unmarked 145 | 2. For each unmarked vertex *v*, run DFS to identity all vertices discovered as 146 | part of the same component 147 | 3. Create a hash map, using vertex as the key, and assign an ID number as value, 148 | then mark the vertex as visited 149 | 4. ID number begins at 1, it should stay at 1 for the whole DFS search. 150 | 5. Upon completion of the first DFS search, ID should increment. 151 | 6. Iterate to next vertex, if it's visited, skip. Once we find an unvisited 152 | node, we fire DFS again but now ID = 2. 153 | 154 | ``` ruby 155 | class ConnectedComponent 156 | def initialize(graph) 157 | @components = Hash.new 158 | @graph = graph 159 | mapConnectedComponents 160 | end 161 | 162 | def mapConnectedComponents 163 | id = 1 164 | @graph.vertices.each do |vertex| 165 | unless vertex.visited? 166 | dfs(vertex, id) 167 | id += 1 168 | end 169 | end 170 | @count = id 171 | end 172 | 173 | def count 174 | @count 175 | end 176 | 177 | def dfs(vertex, id) 178 | # should be something like... 179 | @components[vertex] = id 180 | vertex.visit! 181 | vertex.out_edges.each do |edge| 182 | dfs(edge.to_vertex, id) 183 | end 184 | end 185 | end 186 | ``` 187 | 188 | ## Interivew Problems 189 | __Route Between Nodes__(CTCI): Given a directed graph, design an algorithm to find out 190 | whether there is a route between two nodes 191 | ``` ruby 192 | def is_there_route?(source, dest) 193 | dfs(source, dest) == dest 194 | end 195 | 196 | def dfs(node, target) 197 | return nil if node.nil? || node.visit? 198 | node.visit! 199 | return node if node == target 200 | node.out_edges.each do |out_edge| 201 | search_result = dfs(out_edge.to_vertex, target) 202 | return search_result unless search_result.nil? 203 | end 204 | nil 205 | end 206 | ``` 207 | 208 | __Deep Copy of a Graph__: Given a node, and this node contains other nodes, 209 | they together form a graph. Write a function to deep copy this node and 210 | everything inside it. 211 | ``` 212 | Example 1 213 | A contains B and C. B & C contain nothing 214 | A.nodes = [B, C] 215 | B.nodes = [] 216 | C.nodes = [] 217 | 218 | Example 2 219 | A contains itself 220 | A.nodes = [A] 221 | 222 | Example 3 223 | Cyclic Graph 224 | A.nodes = [B] 225 | B.nodes = [C] 226 | C.nodes = [A] 227 | 228 | Example 4 => What is the time complexity of your algorithm, given this example? 229 | D.nodes = [E, F, D, E, E, E, E, F] 230 | E.nodes = [G, H] 231 | F.nodes = [] 232 | G.nodes = [] 233 | H.nodes = [] 234 | ``` 235 | 236 | ``` ruby 237 | # N = number of nodes 238 | # M = sum of the sizes of all node lists 239 | # Time: O(N + M) 240 | # Space: O(N) + O(M) 241 | class Node 242 | def initialize 243 | @nodes = [] 244 | end 245 | 246 | def deepCopy(dup_record = Hash.new) 247 | new_node = Node.new 248 | dup_record[self] = new_node 249 | self.nodes.each do |node| 250 | # It's already been copied, so use the copied version 251 | if dup_record[node] 252 | new_node.nodes << dup_record[node] 253 | else 254 | # It hasn't been copied, so make a copy 255 | new_node.nodes << node.deepCopy(dup_record) 256 | end 257 | end 258 | new_node 259 | end 260 | end 261 | ``` 262 | -------------------------------------------------------------------------------- /doc/harvard-web-scalability-lecture.md: -------------------------------------------------------------------------------- 1 | ## VPS 2 | Virual Private Server - shared hardware but individual copy of softwares 3 | * DreamHost 4 | * Go Daddy 5 | * Host Gator 6 | * Linode 7 | * pair Networks 8 | * Slicehost 9 | * VPSLAND 10 | 11 | ## Amazon Web Service EC2 12 | Automate spawning new web servers 13 | 14 | # Scalability 15 | ## Vertical 16 | * CPU 17 | * More cores 18 | * Disk 19 | * More space 20 | * PATA, SATA, SAS, more bandwidth, more RPM - 15,000 21 | * RAM 22 | * Process more at once 23 | 24 | ## Horizontal 25 | Bunch of more cheaper machines, but the problem is, where should client requests 26 | go? 27 | ### Distribute Requests 28 | #### Load Balancing 29 | When request comes in, the DNS will map domain name to IP address of the 30 | black box load balancer. In this case, the application servers themselves don't 31 | need public IP address. Rest of the world won't be able to see the actual 32 | IP location of the application nodes. 33 | 34 | We can either send the request to least busy server. We can use round robin to 35 | distribute the load evenly. This load balancer is just a fancy DNS server with 36 | built-in logic that distributes load. 37 | 38 | Example: 39 | * Software 40 | * ELB 41 | * HAProxy 42 | * LVS 43 | 44 | * Hardware 45 | * Barracuda 46 | * Cisco 47 | * Citrix 48 | * F5 49 | 50 | $100,000 for a load balancer or generally for a pair... 51 | 52 | #### What's the catch? 53 | Just by bad luck, one of the servers gets a computationally heavy task. There 54 | is no point of querying the DNS server every single time we make a request. In 55 | fact, typically the browser will cache the response and record the IP address 56 | of the website I typically visit. If you are one of the power users that consumes 57 | large amount of resources on server A, you will stay on server A while the next 58 | guy will get send to server B, C, D or whatever. But it doesn't solve the real 59 | problem. 60 | 61 | More sophisticated approach, would be making decision by server load. 62 | 63 | Also as we scale, data will become desynchronized and if every user gets directed 64 | to a new place every time he gets distributed to a new IP address, to a new server. 65 | Big pain in the butt 66 | 67 | #### RAID 68 | Redundant Array of Inexpensive Disk - multiple hard drives 69 | RAID-0 - two identical hard drives, spin A a bit write some info to it, then spin B a bit write some info on it and this is known as striping 70 | RAID-1 - two mirror hard drives 71 | 72 | #### Make many copies 73 | The application server does not store the session token, pass it onto a distributed filesystem. 74 | Using the idea of replication to make multiple storage of the data. 75 | 76 | ### Sticky Sessions 77 | Many complex web applications store state locally and can fail or underperform if a load balancer distributes requests in a user’s session across different servers instead of directing all requests to the server that responded to the initial request. 78 | 79 | When session persistence is configured, the NGINX Plus load balancer identifies user sessions and pins all requests in a session to the same upstream server. This avoids fatal errors for applications that only store state locally, and it significantly improves performance for applications that share state across a cluster. 80 | 81 | Despite HTTP being a ‘stateless’ protocol, many applications store state locally and don’t perform well in a load-balanced environment. Once a user commences a complex transaction with an upstream server (for example, putting the first item in his shopping basket, or starting a paginated search), it is often optimal to direct all of the user’s requests to that same server, because sharing state between servers is slow or even impossible. 82 | 83 | NGINX Plus tracks user sessions and pins them to the correct upstream server. It provides three methods for identifying user sessions: Inserting its own tracking cookie into the traffic, Learning when servers create a session and detecting requests in that session, and tracking specific data in a request (such as the jvmRoute). 84 | 85 | But if you must use server-local session state, sticky sessions are definitely the way to go-- and even if you don't use server-local session state, stickiness has benefits when it comes to cache utilization (see above). Your load balancer should be able to look at HTTP cookies (not only IP address) to determine stickiness, since IP addresses can change during a single session (e.g. docking a laptop between a wired and wireless network). 86 | 87 | Even better, don't use session state on the web server at all! If session state is very painful to lose (e.g. shopping carts), store it in a central database and clear out old sessions periodically. If session state is not critical (e.g. username/avatar URL), then stick it in a cookie-- just make sure you're not shoving too much data into the cookie. 88 | 89 | Modern versions of Rails, by default, store session variables in a cookie for the reasons above. Other web frameworks may have a "store in cookie" and/or "store in DB" option. 90 | 91 | The implementation detail of sticky session is implemented in the load balancer. The load balancer 92 | remembers a number mapping scheme that maps a particular request to the same physical server. 93 | 94 | 95 | ## Authentications 96 | In *Session-based Authentication* the Server does all the heavy lifting server-side. Broadly speaking a client authenticates with its credentials and receives a session_id (which can be stored in a cookie) and attaches this to every subsequent outgoing request. So this could be considered a "token" as it is the equivalent of a set of credentials. There is however nothing fancy about this session_id-String. It is just an identifier and the server does everything else. It is stateful. It associates the identifier with a user account (e.g. in memory or in a database). It can restrict or limit this session to certain operations or a certain time period and can invalidate it if there are security concern and more importantly it can do and change all of this on the fly. Furthermore it can log the users every move on the website(s). Possible disadvantages are bad scale-ability (especially over more than one server farm) and extensive memory usage. 97 | 98 | In *Token-based Authentication* no session is persisted server-side (stateless). The initial steps are the same. Credentials are exchanged against a token which is then attached to every subsequent request (It can also be stored in a cookie). However for the purpose of decreasing memory usage, easy scale-ability and total flexibility (tokens can be exchanged with another client) a string with all the necessary information is issued (the token) which is checked after each request made by the client to the server. There are a number of ways to use/ create tokens: 99 | 100 | ## MemCached 101 | Why is Craiglist delivering HTML file directly 102 | They are storing html file directly !!! So that they don't have to regenerate HTML file 103 | everytime we re-visit it. This is a way of caching. 104 | 105 | Why is Craiglist stuck in the 90s? If you want to change the appearance of the file, 106 | since HTML pages were directly stored in the data bases, there is no trivial engineering way to 107 | modify these HTML files. 108 | 109 | Connect to MemCached through its server 110 | Disk is slow 111 | MySQL provides a layer of cache 112 | Memory Cache Daemon -> MemCacheD 113 | If the item is null in our MemCacheD, then we hit the SQL database and make a query on the 114 | item. Then we store a key and the item in the cache. 115 | MemCache is this key-value pair storage. 116 | 117 | ### Facebook 118 | These days, Facebook is still read heavy. Facebook uses MemCacheD quite a bit. 119 | 120 | ## Database 121 | Replication is all about making automatic copies of something. A master database 122 | which is where you read data from and write data to but just for good measure that 123 | master has one or more slave databases attached to it. This will solve the 124 | read heavy issue because we can distribute load. Load balancing for database! 125 | Slaves are either for redundancy or for balancing read quests. 126 | 127 | What is the fault in this layout? 128 | What if the master dies? We need multiple masters. We propagate queries across 129 | masters. This will help us with writing. 130 | 131 | ## Partitoning 132 | Still have redundancy, balance load based on high level user information 133 | ## High Availability 134 | high availability refers to some kind of relationship between a pair or more of servers that are checking each 135 | other's heart beat and if someone in the network dies, the other will take over the full control. 136 | 137 | DNS at Geography level 138 | -------------------------------------------------------------------------------- /doc/hash_map.md: -------------------------------------------------------------------------------- 1 | # Hash map 2 | 3 | ## Implementation 4 | ``` ruby 5 | class HashMap 6 | include Enumerable 7 | 8 | attr_reader :count 9 | 10 | def initialize(num_buckets = 8) 11 | @store = Array.new(num_buckets) { LinkedList.new } 12 | @count = 0 13 | end 14 | 15 | def include?(key) 16 | bucket(key).include?(key) 17 | end 18 | 19 | def set(key, val) 20 | delete(key) if include?(key) 21 | resize! if @count >= num_buckets 22 | 23 | @count += 1 24 | bucket(key).insert(key, val) 25 | end 26 | 27 | def get(key) 28 | bucket(key).get(key) 29 | end 30 | 31 | def delete(key) 32 | removal = bucket(key).remove(key) 33 | @count -= 1 if removal 34 | removal 35 | end 36 | 37 | def each 38 | @store.each do |bucket| 39 | bucket.each { |link| yield [link.key, link.val] } 40 | end 41 | end 42 | 43 | def to_s 44 | pairs = inject([]) do |strs, (k, v)| 45 | strs << "#{k} => #{v}" 46 | end 47 | "{\n" + pairs.join(",\n") + "\n}" 48 | end 49 | 50 | alias_method :inspect, :to_s 51 | alias_method :[], :get 52 | alias_method :[]=, :set 53 | 54 | private 55 | 56 | def num_buckets 57 | @store.length 58 | end 59 | 60 | def resize! 61 | old_store = @store 62 | @store = Array.new(num_buckets * 2) { LinkedList.new } 63 | @count = 0 64 | 65 | old_store.each do |bucket| 66 | bucket.each { |link| set(link.key, link.val) } 67 | end 68 | end 69 | 70 | def bucket(key) 71 | @store[key.hash % num_buckets] 72 | end 73 | end 74 | ``` 75 | 76 | ## Interview Problems 77 | `consecutive_sequence` - Implement a function to determine the longest 78 | consecutive sequence of integers from an array of integers. For example, 79 | [1, 2, 5, 4, 3] has a sequence of 5. [100, 1, 20, 4, 3, 2] has a sequence 80 | of 4 because of {1, 2, 3, 4}. Notice that the integers do not appear in order. 81 | Write an algorithm that will complete this task in O(n) time. 82 | You may assume that there can only be one sequence 83 | 84 | ``` ruby 85 | # HINT: 86 | # first iteration: add the integers to a set 87 | # second iteration: delete an integer from the set if it has a consecutive partner 88 | ``` 89 | 90 | __Hash Dictionary__: Suppose a hash representing a directory. All keys are 91 | strings with names for either folders or files. Keys that are folders point 92 | to nested hashes. Keys that are files point to "true". Write a function 93 | that takes such a hash and returns an array of strings with the path to 94 | each file in the hash. 95 | 96 | __Example__: 97 | ``` 98 | files = { 99 | 'a' => { 100 | 'b' => { 101 | 'c' => { 102 | 'd' => { 103 | 'e' => true 104 | }, 105 | 106 | 'f' => true 107 | } 108 | } 109 | } 110 | } 111 | 112 | file_list(files) # => ['a/b/c/d/e', 'a/b/c/f'] 113 | ``` 114 | ``` ruby 115 | def file_list(hash) 116 | files = [] 117 | hash.each do |item, nested_item| 118 | if nested_item.is_a?(Hash) 119 | folder = item 120 | nested_files = file_list(nested_item) 121 | nested_files.each { |file| files << "#{folder}/#{file}" } 122 | else 123 | files << item 124 | end 125 | end 126 | files 127 | end 128 | ``` 129 | ``` 130 | Walk through example: 131 | files = { 132 | 'a' => { 133 | 'b' => true, 134 | 'c' => true 135 | } 136 | } 137 | 138 | file_list(files) 139 | Initialize files => [], an empty array 140 | files.each do |item, nested_item| 141 | item = 'a' 142 | nested_item = {'b' => true, 'c' => true} 143 | Set folder = 'a' 144 | Since nested_item is a hash, we open another stack and call: 145 | file_list({'b' => true, 'c' => true}) 146 | Initialize files => [] 147 | files.each do |item, nested_item| 148 | item = 'b', 'c' 149 | nested_item is not a hash, so items will go into files 150 | return ['b', 'c'] 151 | Now nested_files = ['b', 'c'] 152 | nested_files.each do |file| 153 | files << 'a/b' 154 | files << 'a/c' 155 | and etc... 156 | 157 | Another example: 158 | files = { 159 | 'a' => { 160 | 'b' => { 'c' => true }, 161 | 'd' => { 'e' => true, 'f' => true } 162 | } 163 | } 164 | Initialize files => [] 165 | files.each do |item, nested_item| 166 | item = 'a' 167 | nested_item = { 168 | 'b' => { 'c' => true }, 169 | 'd' => { 'e' => true, 'f' => true } 170 | } 171 | Set folder = 'a' 172 | Since nested_item is a hash, open another stack and make call: 173 | file_list({'b' => { c => true }, 'd' => { 'e' => true, 'f' => true }}) 174 | Initialize files = [] 175 | files.each do |item, nested_item| 176 | item = 'b', 'd' 177 | nested_item => { 'c' => true }, { 'e' => true, 'f' => true } 178 | Set folder = 'b' and 'd' 179 | Since nested_item is a hash again, open another stack and call: 180 | file_list(...) 181 | Initialize files = [] 182 | files.each do |item, nested_item| 183 | item = c, e, f 184 | return ['c'], ['e', 'f'] 185 | Now we can do nested_files.each |file| 186 | files << "b/c" 187 | files << "c/e" 188 | files << "c/f" 189 | return files = ["b/c", "c/e", "c/f"] 190 | We have those nested files at this stack now, ["b/c", "c/e", "c/f"], do 191 | nested_files.each do |file| again 192 | ["a/b/c", "a/c/e", "a/c/f"] is the final result 193 | ``` 194 | -------------------------------------------------------------------------------- /doc/http.md: -------------------------------------------------------------------------------- 1 | # HTTP - HyperText Transfer Protocol 2 | 3 | ## Standard Question: What happens when you enter google.com into address bar 4 | 1. Computer needs to translate hostname www.google.com into an IP address 5 | * IP - Internet Protocol 6 | 2. It needs to perform a domain name look up using a DNS (Domain Name System) 7 | * DNS servers are everywhere. 8 | * Google has its own DNS servers 9 | * College has its own DNS servers 10 | * Big company has its own DNS servers 11 | 3. DNS has a hierarchy: no single node knows all domain name mapping. However, 12 | internet service provider knows some bigger fish and maybe that bigger fish knows 13 | an even bigger fish that knows where the domain name can be found. 14 | 4. If the worst happens, no fish knows where your domain name belongs, then there 15 | are root servers which are spread out geographically across several continents 16 | * root servers know which authority is responsible for keeping track of all domain name purchases 17 | 5. Once a valid IP address is found, and a connection is established. An envelop of http requests 18 | will be sent to Google application server. 19 | 6. The server receives the packet and respond accordingly to the HTTP method in the header. 20 | 21 | ### The HTTP Envelop 22 | ``` 23 | 123.123.123.123 24 | GET / HTTP/1.0 25 | http://www.google.com 26 | ``` 27 | `http://` is known as the schema. It indicates to a piece of software how it should 28 | view the contents at that address 29 | 30 | ## IP Address 31 | ### Private vs Public 32 | Anything starts with 192.168.x.y is typically a private IP address. 33 | Outsiders can not access local host, because typical home modem prevents private 34 | IP address from leaking out to the public because these private IP addresses are not 35 | uniquely identifiable 36 | 37 | ### Two Protocols (TCP/IP) 38 | IP is a set of conventions that govern how you associate numeric addresses with computers 39 | TCP is the standard that web browsers and web servers speak in order to actually physically 40 | move data or electronically move data from point A to point B using the higher notion 41 | of an IP address to actually uniquely identify points A and B 42 | 43 | The number 80 will arbitrarily but consistently identified this service. If you have a 44 | server and you have a website, it is running so to speak on port 80. It's listening on port 80. 45 | You might also have email service. If you want to be able to send email to gmail, you need to use 46 | TCP but on port 25. 47 | 48 | ### Port Forwarding 49 | If your home network has a public IP address, and you usually get one from your internet service provider. 50 | Your individual laptop on which you've created your final project is that one of these private ip addresses. 51 | What you can do is configure your home router AKA firewall AKA cable modem, it depends on what make 52 | and model you have, but that device, you can configure it to say any internet traffic that comes 53 | from the internet to my home on my public IP address destined for port 80 should be "port forwarded" 54 | to IP address 192.168.x.y port 80. In other words you can tell this machine to take incoming on that port 55 | and route it very specifically to this computer of yours. 56 | 57 | Many non-standard ports are blocked for security reason. But it's a joke... all you have to do is 58 | to change the port number to be 80 or 443. 80 is for TCP and 443 is for UDP 59 | 60 | ## Domain Name 61 | Beside registering a domain name and mapping it to an IP address, every web server should have 62 | at least two DNS server. One is primary and another one is secondary for fail over. This is where 63 | web hosting companies come in. Web hosting companies will provide DNS servers and web server for individuals 64 | who wish to push their code online. 65 | DNS server's database is a giant excel sheet 66 | 67 | There are different types of rows. So one of those rows can be an official record that says the name server, 68 | NS, for this domain is whatever IP address web host gave me. 69 | 70 | * A - row type a: domain name maps to IP address 71 | * CNAME - alias record: domain name maps to domain name, added extra layer of abstraction 72 | * MX - mail exchange record: 73 | 74 | ## Shared Web Host 75 | 76 | ## Virtual Private Servers (VPS) 77 | Shared hardware but not shared software 78 | 79 | ## Secure Shell (SSH) 80 | A way to connect to a remote server and execute commands on it 81 | 82 | ## DHCP 83 | Dynamic Host Configuration Protocol - dynamically distributing network configuration parameters, 84 | such as IP addresses for interfaces and services. 85 | -------------------------------------------------------------------------------- /doc/java.md: -------------------------------------------------------------------------------- 1 | # Java 2 | Java is an object-oriented, class-based, static typed language. Syntactically it looks 3 | like JavaScript but it works like Ruby. This is a very brief introduction, just for 4 | the sake of reading solutions from Cracking the Coding Interview. 5 | 6 | ## Class Declaration 7 | ``` java 8 | //public means it's accessible to the outside world 9 | public class Animal { 10 | //Declaring instance variable as private, setter and getter are needed 11 | private int age; 12 | //Must specify the type of the variable for declaration 13 | //This is a constructor function for instantiating an instance of Animal 14 | public Animal(int num) { 15 | age = num; 16 | } 17 | } 18 | ``` 19 | 20 | ## Function and Method Declaration 21 | ``` java 22 | //static means it is class function 23 | public static void sayHello() { 24 | System.out.println("Hello"); 25 | } 26 | 27 | //non-static means it is an instance method 28 | public void sayHello() { 29 | System.out.println(name + " says hello"); 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /doc/linked_list.md: -------------------------------------------------------------------------------- 1 | # Linked List 2 | LinkedList is ideal for fast insertion/deletion. It may not offer an efficient 3 | lookup like indexing into an array but if items are constantly removed/added, then 4 | it's better to use LinkedList instead of an array. Inserting into middle of an array 5 | takes O(n) time, because all trailing elements need to copy over to next spot. 6 | LinkedList also maintains order. 7 | 8 | ## Implementation 9 | ### Doubly Linked List 10 | ``` ruby 11 | class Link 12 | attr_accessor :key, :val, :next, :prev 13 | 14 | def initialize(key = nil, val = nil) 15 | @key = key 16 | @val = val 17 | @next = nil 18 | @prev = nil 19 | end 20 | 21 | def to_s 22 | "#{@key}: #{@val}" 23 | end 24 | end 25 | 26 | class LinkedList 27 | include Enumerable 28 | 29 | def initialize 30 | @head = Link.new 31 | @tail = Link.new 32 | @head.next = @tail 33 | @tail.prev = @head 34 | end 35 | 36 | def [](i) 37 | each_with_index { |link, j| return link if i == j } 38 | nil 39 | end 40 | 41 | def first 42 | empty? ? nil : @head.next 43 | end 44 | 45 | def last 46 | empty? ? nil : @tail.prev 47 | end 48 | 49 | def empty? 50 | @head.next == @tail 51 | end 52 | 53 | def get(key) 54 | each { |link| return link.val if link.key == key } 55 | nil 56 | end 57 | 58 | def include?(key) 59 | any? { |link| link.key == key } 60 | end 61 | 62 | def insert(key, val) 63 | each { |link| return link.val = val if link.key == key } 64 | 65 | new_link = Link.new(key, val) 66 | current_last = @tail.prev 67 | 68 | current_last.next = new_link 69 | new_link.prev = current_last 70 | new_link.next = @tail 71 | @tail.prev = new_link 72 | 73 | new_link 74 | end 75 | 76 | def remove(key) 77 | each do |link| 78 | if link.key == key 79 | link.prev.next = link.next 80 | link.next.prev = link.prev 81 | link.next, link.prev = nil, nil 82 | return link.val 83 | end 84 | end 85 | 86 | nil 87 | end 88 | 89 | def each 90 | current_link = @head.next 91 | until current_link == @tail 92 | yield current_link 93 | current_link = current_link.next 94 | end 95 | end 96 | 97 | def to_s 98 | inject([]) { |acc, link| acc << "[#{link.key}, #{link.val}]" }.join(", ") 99 | end 100 | end 101 | ``` 102 | ### Singly Linked List 103 | Occasionally, actually only once for me, that singly linked list will show up on an 104 | interview. Singly linked list seems easier to implement that doubly linked list on a 105 | first glance but deletion in singly linked list requires a second thought. 106 | 107 | Each link inside a singly linked list does not have a pointer to its previous link. That 108 | is problematic for deletion because when you try to remove a link, you need to connect 109 | its previous link to its next link. Thus, we need two link runners. 110 | 111 | ``` ruby 112 | class Link 113 | attr_accessor :val, :next 114 | def initialize(val) 115 | @val = val 116 | @next = nil 117 | end 118 | end 119 | 120 | class SinglyLinkedList 121 | attr_reader :head, :tail 122 | 123 | def initialize 124 | @head = Link.new("head") 125 | @tail = @head 126 | end 127 | 128 | def is_empty? 129 | @head == @tail 130 | end 131 | 132 | def first_link 133 | is_empty? ? null : @head.next 134 | end 135 | 136 | def insert(val) 137 | new_link = Link.new(val) 138 | @tail.next = new_link 139 | @tail = new_link 140 | end 141 | 142 | def delete(val) 143 | if is_empty? 144 | raise "linked list is empty" 145 | else 146 | this_link = @head.next 147 | prev_link = @head 148 | while this_link != @tail 149 | if this_link.val == val 150 | prev_link.next = this_link.next 151 | this_link = this_link.next 152 | else 153 | prev_link = this_link 154 | this_link = this_link.next 155 | end 156 | end 157 | 158 | if @tail.val == val 159 | @tail = prev_link 160 | prev_link.next = nil 161 | end 162 | end 163 | end 164 | 165 | def to_s 166 | str = "" 167 | current_link = @head 168 | while current_link != @tail 169 | str += current_link.val.to_s + " " 170 | current_link = current_link.next 171 | end 172 | str + @tail.val.to_s 173 | end 174 | end 175 | ``` 176 | 177 | 178 | ## Interview Problems 179 | `remove_dups` - write code to remove duplicates from an unsorted linked list 180 | How would you solve this problem if a temporary buffer is not allowed? 181 | ``` ruby 182 | def remove_dups(linked_list) 183 | curr_link = linked_list.head 184 | record = Hash.new 185 | until curr_link == linked_list.tail 186 | if record[curr_link.value] 187 | curr_link.prev.next = curr_link.next 188 | curr_link.next.prev = curr_link.prev 189 | else 190 | record[curr_link.value] = true 191 | end 192 | curr_link = curr_link.next 193 | end 194 | linked_list 195 | end 196 | 197 | # If hash is not allowed, use an O(N^2) approach 198 | # For every iteration of current link, create a runner_link that checks the 199 | # subsequent links for duplicates 200 | ``` 201 | 202 | `return_kth_to_last` - implement an algorithm to find the kth to last element 203 | of a singly linked list 204 | ``` ruby 205 | # Assume that we don't know the size of the linked list. First compute its size 206 | # by going through the list. Then find the k-th element. The more optimal one 207 | # would be creating two pointers. We place them k nodes apart. Then, when 208 | # we move them at the same pace, p2 will hit the end and p1 will be the target 209 | 210 | # 01 02 03 04 05 06 07 08 09 211 | # p1 p2 212 | # p2 p2 213 | # ... 214 | # p1 p2 215 | def return_kth_to_last(k, list) 216 | p1 = list.first 217 | p2 = list.first 218 | 219 | i = 0 220 | while i < k 221 | return nil if p2 == nil 222 | p2 = p2.next 223 | i += 1 224 | end 225 | 226 | while p2 != list.last 227 | p1 = p1.next 228 | p2 = p2.next 229 | end 230 | p1 231 | end 232 | ``` 233 | 234 | `sum_lists` - you have two numbers represented by a linked list, where each node 235 | contains a single digit. The digits are stored in reverse order, such that 236 | 1's digit is at the head of the list. Write a function that adds the two 237 | numbers and returns the sum as a linked list. 238 | Example: 239 | Input: (7 -> 1 -> 6) + (5 -> 9 -> 2) = 617 + 295 240 | 241 | ``` ruby 242 | def sum_lists(list1, list2) 243 | resultant_list = LinkedList.new 244 | runner1 = list1.head 245 | runner2 = list2.head 246 | carry_over = 0 247 | until runner1.nil? && runner2.nil? 248 | if runner1.nil? 249 | digit_sum = runner2.value + carry_over 250 | elsif runner2.nil? 251 | digit_sum = runner1.value + carry_over 252 | else 253 | digit_sum = runner1.value + runner2.value + carry_over 254 | end 255 | carry_over = digit_sum/10 256 | digit_sum = digit_sum % 10 257 | resultant_list.insert(digit_sum) 258 | 259 | runner1 = runner1.next unless runner1.nil? 260 | runner2 = runner2.next unless runner2.nil? 261 | end 262 | resultant_list 263 | end 264 | ``` 265 | -------------------------------------------------------------------------------- /doc/lru.md: -------------------------------------------------------------------------------- 1 | ## Least Recently Use LRU Cache 2 | ### Key Idea 3 | It uses HashMap and LinkedList. Every key in the HashMap points to a single 4 | link. The links form a LinkedList. The list is used for preserving the chronological order of the links. The head is the least recently used link, while the tail is the most recently used link. HashMap is used for fast retrieval. 5 | 6 | ``` ruby 7 | class LRUCache 8 | def initialize(max, prc) 9 | @map = HashMap.new 10 | @store = LinkedList.new 11 | @max = max 12 | @prc = prc 13 | end 14 | 15 | def count 16 | @map.count 17 | end 18 | 19 | def get(key) 20 | if @map[key] 21 | link = @map[key] 22 | update_link!(link) 23 | link.val 24 | else 25 | calc!(key) 26 | end 27 | end 28 | 29 | def to_s 30 | "Map: " + @map.to_s + "\n" + "Store: " + @store.to_s 31 | end 32 | 33 | private 34 | 35 | def calc!(key) 36 | val = @prc.call(key) 37 | new_link = @store.insert(key, val) 38 | @map[key] = new_link 39 | 40 | eject! if count > @max 41 | val 42 | end 43 | 44 | def update_link!(link) 45 | link.prev.next = link.next 46 | link.next.prev = link.prev 47 | 48 | link.prev = @store.last 49 | link.next = @store.last.next 50 | @store.last.next = link 51 | end 52 | 53 | def eject! 54 | rm_link = @store.first 55 | rm_link.prev.next = rm_link.next 56 | rm_link.next.prev = rm_link.prev 57 | @map.delete(rm_link.key) 58 | nil 59 | end 60 | end 61 | ``` 62 | -------------------------------------------------------------------------------- /doc/manacher.md: -------------------------------------------------------------------------------- 1 | # Longest Palindromic Substring 2 | 3 | The worst solution is O(n^3) which is to find all the substrings and then run 4 | `isPalindrome?` on them. 5 | Finding substrings is O(n^2) and for each substring we run O(n) check 6 | 7 | This is a slightly better naive solution with O(n^2) 8 | ``` ruby 9 | def longest_p_substring(str) 10 | longest = "" 11 | (0...str.length).each do |idx| 12 | left = idx - 1 13 | right = idx + 1 14 | odd_palindrome = [str[idx]] 15 | # You don't have to use array, but it feels more intuitive than using pure indices 16 | while left > 0 && right < str.length && str[left] == str[right] 17 | odd_palindrome.unshift(str[left]) 18 | odd_palindrome.push(str[right]) 19 | left -= 1 20 | right += 1 21 | end 22 | longest = odd_palindrome.join if odd_palindrome.length > longest.length 23 | 24 | left = idx 25 | right = idx + 1 26 | even_palindrome = [] 27 | while left > 0 && right < str.length && str[left] == str[right] 28 | even_palindrome.unshift(str[left]) 29 | even_palindrome.push(str[right]) 30 | left -= 1 31 | right += 1 32 | end 33 | longest = even_palindrome.join if even_palindrome.length > longest.length 34 | end 35 | longest 36 | end 37 | ``` 38 | So what's so bad about the solution above? The issue is that we are expanding 39 | for every center. If we don't have to expand for the unnecessary situation, then 40 | we can get the solution down to O(n) time 41 | 42 | O(n) solution => Manacher's Algorithm 43 | ``` ruby 44 | def manacher(str) 45 | t = "$##{str.chars.join('#')}#@" 46 | # pali is responsible for holding the length of palindrome centered at a given index 47 | pali = Array.new(t.length){0} 48 | # center and right boundary indices start at 0 49 | ctr, right = 0, 0 50 | (1...t.length - 1).each do |idx| 51 | mirr = 2*ctr - idx 52 | # Copy palindrome length from mirror, if idx is still within right boundary 53 | # Or else, use right - idx if pali[mirr] expands beyond the left boundary 54 | if idx < right 55 | pali[idx] = [right - idx, pali[mirr]].min 56 | end 57 | # Now we know we have at least pali[mirr] length for palindrome so we can skip checking those ones 58 | while t[idx + (1 + pali[idx])] == t[idx - (1 + pali[idx])] 59 | pali[idx] += 1 60 | end 61 | # if the palindrome at current index expands beyond the right boundary, we need to 62 | # reset the right boundary and re-center at idx 63 | if idx + pali[idx] > right 64 | ctr = idx 65 | right = idx + pali[idx] 66 | end 67 | end 68 | pali 69 | end 70 | ``` 71 | -------------------------------------------------------------------------------- /doc/merge_sort.md: -------------------------------------------------------------------------------- 1 | # Merge Sort 2 | 3 | ## Implementation 4 | #### Ruby 5 | ``` ruby 6 | class MergeSort 7 | def self.sort(array) 8 | return array if array.length < 2 9 | middle = array.length/2 10 | left, right = array.take(middle), array.drop(middle) 11 | sorted_left, sorted_right = MergeSort.sort(left), MergeSort.sort(right) 12 | MergeSort.merge(sorted_left, sorted_right) 13 | end 14 | 15 | def self.merge(left, right) 16 | merged_array = [] 17 | until left.empty? || right.empty? 18 | merged_array << ((left.first < right.first) ? left.shift : right.shift) 19 | end 20 | merged_array + left + right 21 | end 22 | end 23 | ``` 24 | #### JavaScript 25 | ``` javascript 26 | function mergeSort(array) { 27 | if (array.length < 2) { 28 | return array; 29 | } else { 30 | let midIndex = Math.floor(array.length/2); 31 | let left = array.slice(0, midIndex); 32 | let right = array.slice(midIndex); 33 | return merge(mergeSort(left), mergeSort(right)); 34 | } 35 | } 36 | 37 | function merge(left, right) { 38 | let merged = []; 39 | while (left.length > 0 && right.length > 0) { 40 | if (left[0] <= right[0]) { 41 | merged.push(left.shift()); 42 | } else { 43 | merged.push(right.shift()); 44 | } 45 | } 46 | return merged.concat(left, right); 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /doc/object_oriented_design.md: -------------------------------------------------------------------------------- 1 | # Object-Oriented Design 2 | 3 | ## Approach 4 | 1. Handle Ambiguity 5 | * Ask the six W's, who, what, where, when, how and why? 6 | 7 | 2. Define the Core Objects 8 | * Make a list of all the important component/objects for the system. An 9 | object deserves to be an object only when it should have its own state 10 | 11 | 3. Analyze Relationships 12 | * Think about inheritances and what are some common features and functions 13 | that we can put into a module instead of a class 14 | 15 | 4. Investigate Actions 16 | * Figure out the key actions and functions of each component/object. 17 | 18 | ## Design Patterns 19 | ### Singleton Class 20 | A singleton pattern ensures that a class has only one instance and ensures 21 | access to the instance through the application. For example, stores in Flux 22 | architecture are singleton, every type of data should have only one instance 23 | of store. 24 | 25 | ### Factory Method 26 | Factory method offers an interface for creating an instance of a class, with 27 | its subclasses deciding which class to instantiate. 28 | -------------------------------------------------------------------------------- /doc/quick_sort.md: -------------------------------------------------------------------------------- 1 | # Quick Sort 2 | 3 | ## Implementation 4 | #### Ruby 5 | ``` ruby 6 | # Not in place, use O(n) memory 7 | class QuickSort 8 | def self.sort(array) 9 | return array if array.empty? 10 | pivot_idx = rand(array.length) 11 | array[0], array[pivot_idx] = array[pivot_idx], array[0] 12 | 13 | pivot = array.first 14 | left = array.select {|el| pivot > el} 15 | middle = array.select {|el| pivot == el} 16 | right = array.select {|el| pivot < el} 17 | 18 | QuickSort.sort(left) + middle + QuickSort.sort(right) 19 | end 20 | end 21 | ``` 22 | The pivot is chosen randomly by selecting a random index in the array and 23 | swap the random element with the first element. Due to recursion, this method 24 | will take O(n) space. We can improve it by sorting in place. 25 | ``` ruby 26 | class QuickSort 27 | def self.in_place_sort!(array, start = 0, length = array.length, &prc) 28 | prc ||= Proc.new {|el1, el2| el1 <=> el2} 29 | return array if length < 2 30 | 31 | pivot_idx = partition(array, start, length, &prc) 32 | left_length = pivot_idx - start 33 | right_length = length - (left_length + 1) 34 | 35 | in_place_sort!(array, start, left_length, &prc) 36 | in_place_sort!(array, pivot_idx + 1, right_length, &prc) 37 | array 38 | end 39 | 40 | def self.partition(array, start, length, &prc) 41 | prc ||= Proc.new {|el1, el2| el1 <=> el2} 42 | pivot_idx, pivot = start, array[start] 43 | ((start + 1)...(start+length)).each do |idx| 44 | val = array[idx] 45 | if prc.call(pivot, val) == 1 46 | array[idx] = array[pivot_idx + 1] 47 | array[pivot_idx + 1] = pivot 48 | array[pivot_idx] = val 49 | pivot_idx += 1 50 | end 51 | end 52 | end 53 | end 54 | ``` 55 | `partition` will iterate through every index. Whenever `array[idx]` is greater 56 | than the pivot. It will first get replaced with the element after pivot, which 57 | is `array[pivot_idx + 1]`. Then the pivot will take over the spot at `array[pivot_idx + 1]`. 58 | 59 | At last, put the value in pivot's origin position which is `array[pivot_idx]`. 60 | 61 | #### JavaScript 62 | We can throw a callback in for the JavaScript version to achieve the same 63 | generality as above. But this version doesn't need to return any value because 64 | we are modifying the array in place. 65 | ``` javascript 66 | function inPlaceQuickSort(array, startIndex, arrayLength) { 67 | if (arrayLength >= 2) { 68 | let pivotIndex = partition(array, startIndex, arrayLength); 69 | let leftLength = pivotIndex - startIndex; 70 | let rightLength = arrayLength - leftLength - 1; 71 | inPlaceQuickSort(array, startIndex, leftLength); 72 | inPlaceQuickSort(array, pivotIndex + 1, rightLength); 73 | } 74 | } 75 | 76 | function partition(array, startIndex, subArrayLength) { 77 | let pivotIndex = startIndex; 78 | let pivot = array[startIndex]; 79 | for (let i = startIndex + 1; i < startIndex + subArrayLength; i++) { 80 | let val = array[i]; 81 | if (val < pivot) { 82 | array[i] = array[pivotIndex + 1]; 83 | array[pivotIndex + 1] = pivot; 84 | array[pivotIndex] = val; 85 | pivotIndex += 1; 86 | } 87 | } 88 | return pivotIndex; 89 | } 90 | ``` 91 | 92 | ### Tail Recursion 93 | Answer from Quora: 94 | Tail recursion is a special kind of recursion where the recursive call is the very last thing in the function. It's a function that does not do anything at all after recursing. 95 | 96 | This is important because it means that you can just pass the result of the recursive call through directly instead of waiting for it—you don't have to consume any stack space. A normal function, on the other hand, has to have a stack frame so that the compiler knows to come back to it (and have all the necessary variable values) after the recursive call is finished. 97 | 98 | Some languages recognize this and implement "proper tail calls" or "tail call elimination": if they see a recursive call in tail position, they actually compile it into a jump that reuses the current stack frame instead of calling the function normally. This improves the memory usage of the function asymptotically and prevents it from overflowing the stack. With this behavior, tail recursion is actually generally a good thing: chances are you do want to write your functions in a tail-recursive form if you can. 99 | -------------------------------------------------------------------------------- /doc/radix.md: -------------------------------------------------------------------------------- 1 | # Radix Sort 2 | What is a radix? It's the number of unique digits, including zero, used to 3 | represent numbers in positional numeral system. 4 | 5 | | Name | Radix | Bits | Characters | 6 | | ----------- | ----- | ---- | ------------------- | 7 | | binary | 2 | 1 | 01 | 8 | | octal | 8 | 3 | 01234567 | 9 | | decimal | 10 | 4 | 0123456789 | 10 | | hexadecimal | 16 | 4 | 0123456789ABCDEF | 11 | | DNA | 4 | 2 | ACTG | 12 | | lower case | 26 | 5 | abcdefg... | 13 | | upper case | 26 | 5 | ABCDEFG... | 14 | | protein | 20 | 5 | ACDEFGHIKLMNPQRSTVWY| 15 | | base64 | 64 | 6 | ABC...abc...0123... | 16 | | ASCII | 128 | 7 | ASCII characters | 17 | | Unicode | 65536 | 16 | Unicode characters | 18 | 19 | ## Performance of Sorting Algorithms 20 | | Algorithm | Guarantee | Stable? | Space | 21 | | --------- | ------------- | ------- | -------- | 22 | | insertion | N^2 / 2 | yes | 1 | 23 | | mergesort | N log(N) | yes | N | 24 | | quicksort | 1.39 N log(N) | no | c log(N) | 25 | | heapsort | 2 N log(N) | no | 1 | 26 | 27 | Lower bound: ~ N log(N) compares required by any compare-based algorithms 28 | 29 | Can we do better? Yes if we don't depend on comparing keys. 30 | 31 | ## Key-indexed Counting 32 | Assumption: keys are integers between 0 and R - 1 33 | Implication: We can use key as an array index 34 | Goal: Sort an array arr[] of N letters between 0 and R - 1 35 | * Count frequencies of each letter using key as index 36 | * Compute frequency cumulates which specify destination 37 | * Access culumates using key as index to move items 38 | * Copy back into original array 39 | 40 | Java implementation uses static array, which is more efficient, but it requires 41 | one to take care of the indexing. Ruby version is cleaner and more intuitive. 42 | ``` java 43 | int N = a.length; 44 | int[] count = new int[R + 1]; 45 | for (int i = 0; i < N; i++) count[a[i]+1]++; 46 | for (int r = 0; r < R; r++) count[r+1] += count[r]; 47 | for (int i = 0; i < N; i++) aux[count[a[i]]++] = a[i]; 48 | for (int i = 0; i < N; i++) a[i] = aux[i]; 49 | ``` 50 | 51 | It takes O(n) to iterate through the array once and count their frequency 52 | of appearance with a counter array/hash. 53 | It takes O(R + n) to go through the second loop. It has check O(R) times 54 | for the if-statement and (in this case) within those 256 if-statements, it 55 | has to push the elements *n* times into the final array. 56 | ``` ruby 57 | def key_indexed_count(arr) 58 | # ASCII character has a radix of 256 59 | radix = 256 60 | count = [] 61 | result = [] 62 | arr.each do |el| 63 | idx = el.ord 64 | if count[idx] 65 | count[idx] += 1 66 | else 67 | count[idx] = 1 68 | end 69 | end 70 | 71 | (0...radix).each do |idx| 72 | if count[idx] 73 | until count[idx] == 0 74 | result << idx.chr 75 | count[idx] -= 1 76 | end 77 | end 78 | end 79 | result 80 | end 81 | ``` 82 | 83 | ## Two Classifications of Radix Sort 84 | LSD - least significant digit radix sort 85 | MSD - most significant digit radix sort 86 | 87 | LSD string(radix) sort 88 | * Consider characters from right to left 89 | * Stably sort using d-th character as the key(using key-indexed counting) 90 | 91 | ``` ruby 92 | class LSD 93 | # Assuming all strings in the array have same length, which is str_length 94 | def self.sort(str_arr, str_length) 95 | # assuming extended ASCII characters, there are 256 of them 96 | # use Ruby's ord method 97 | radix = 256 98 | (str_length - 1).downto(0) do | d | 99 | buffer = [] 100 | count = [] 101 | # Set everything to be zero, since can't set it to zero by default 102 | (0...radix).each do |idx| 103 | count[idx] = 0 104 | end 105 | str_arr.each do |str| 106 | idx = str[d].ord 107 | count[idx + 1] += 1 108 | end 109 | (0...radix - 1).each do |idx| 110 | count[idx + 1] += count[idx] 111 | end 112 | # the count array will indicate the appropriate index position 113 | # for the sorted string array 114 | (0...str_arr.length).each do |idx| 115 | i = str_arr[idx][d].ord 116 | buffer[count[i]] = str_arr[idx] 117 | count[i] += 1 118 | end 119 | str_arr = buffer 120 | end 121 | str_arr 122 | end 123 | end 124 | ``` 125 | 126 | ## Interview Problems 127 | What is the most efficient way to sort 1 million 32 bit integers? 128 | 129 | __Non Comparison Sort__: 130 | 131 | Part 1: Say that I gave you an array of length n, containing the numbers 1..n in jumbled order. "Sort" this array in O(n) time. You should be able to do this without looking at the input. 132 | 133 | Part 2: Say that I give you an array of length n with numbers in the range 1..N (N >= n). Sort this array in O(n + N) time. You may use O(N) memory. 134 | 135 | Part 3: Say I give you an array of n strings, each of length k. I claim that, using merge sort, you can sort this in O(knlog(n)), since comparing a pair of strings takes O(k) time. 136 | 137 | In theory, I can sort this with O(k*n) time using non comparison radix sort. 138 | ``` ruby 139 | def sort1(jumbled_arr) 140 | (1..(jumbled_arr.length)).to_a 141 | end 142 | 143 | def sort2(array, max_val) 144 | counter = Array.new(max_val + 1, 0) 145 | result = [] 146 | array.each do |el| 147 | counter[el] += 1 148 | end 149 | counter.each_index do |idx| 150 | counter[idx].times { result << idx } 151 | end 152 | result 153 | end 154 | 155 | def sort3(str_arr) 156 | (str_arr.first.length - 1).downto(0) do |d| 157 | str_arr = merge_sort(str_arr, d) 158 | end 159 | str_arr 160 | end 161 | 162 | def merge_sort(arr, idx) 163 | return arr if arr.empty? || arr.length == 1 164 | mid_idx = arr.length/2 165 | left_arr = merge_sort(arr.take(mid_idx), idx) 166 | right_arr = merge_sort(arr.drop(mid_idx), idx) 167 | merge(left_arr, right_arr, idx) 168 | end 169 | 170 | def merge(left, right, i) 171 | sorted_result = [] 172 | until left.empty? || right.empty? 173 | if left[0][i].ord <= right[0][i].ord 174 | sorted_result << left.shift 175 | else 176 | sorted_result << right.shift 177 | end 178 | end 179 | if left.empty? 180 | sorted_result += right 181 | else 182 | sorted_result += left 183 | end 184 | sorted_result 185 | end 186 | ``` 187 | -------------------------------------------------------------------------------- /doc/sql.md: -------------------------------------------------------------------------------- 1 | # Basic SQL Syntax 2 | 3 | ## Data Definition Language (DDL) 4 | Creating table 5 | ``` SQL 6 | CREATE TABLE person ( 7 | id INTEGER PRIMARY KEY AUTO_INCREMENT, 8 | first_name VARCHAR(255) NOT NULL, 9 | last_name VARCHAR(255) NOT NULL, 10 | gender CHAR(1), 11 | birth_date DATE 12 | ); 13 | ``` 14 | Inserting into table 15 | ``` SQL 16 | INSERT INTO 17 | person (first_name, last_name, gender, birth_date) 18 | 19 | VALUES 20 | ("Calvin", "Feng", 'M', '1991-12-23'), 21 | ("Steven", "Jern", "M", '1985-01-01'); 22 | ``` 23 | 24 | ## Data Manipulation Language (DML) 25 | Query using `SELECT` 26 | ``` SQL 27 | SELECT * 28 | FROM person 29 | WHERE first_name = "Calvin" AND birth_date > '1990-01-01' 30 | ``` 31 | 32 | `BETWEEN` operator 33 | ``` SQL 34 | SELECT id, first_name, last_name, start_date 35 | FROM employee 36 | WHERE start_date BETWEEN '2005-01-01' AND '2007-01-01'; 37 | ``` 38 | Using sub-queries 39 | ``` SQL 40 | SELECT id, product_cd, customer_id, avail_balance 41 | FROM account 42 | WHERE product_cd IN ( 43 | SELECT product_cd 44 | FROM product 45 | WHERE product_type_cd = 'ACCOUNT' 46 | ); 47 | ``` 48 | Join tables 49 | ``` SQL 50 | SELECT a.account_id, c.fed_id, e.first_name, e.last_name 51 | FROM employee e INNER JOIN account a 52 | ON e.emp_id = a.open_emp_id 53 | INNER JOIN customer c 54 | ON a.cust_id = c.cust_id 55 | WHERE c.cust_type_cd = 'B'; 56 | ``` 57 | Self join 58 | ``` SQL 59 | SELECT e.first_name, e.last_name, e_manager.first_name, e_manager.last_name 60 | FROM employee e INNER JOIN employee e_manager 61 | ON e.superior_emp_id = e_manager.emp_id 62 | ``` 63 | 64 | 65 | 66 | 67 | ## Web App Database 68 | Heroku uses PostgreSQL and we can find PostgreSQL connection url in the terminal by typing 69 | ``` 70 | heroku pg:credentials DATABASE 71 | ``` 72 | Heroku uses Amazon AWS to host thier PostgreSQL server. 73 | 74 | For example 75 | ![heroku-pg][heroku-pg] 76 | [heroku-pg]: ../img/heroku-postgre.png 77 | 78 | Rails uses pg gem for localhost and the database directory can be found in 79 | here: 80 | ``` 81 | $ cd /usr/local/var/postgres 82 | ``` 83 | 84 | ### Basic Commands in Heroku 85 | #### Upload git to Heroku 86 | ``` 87 | git push heroku master 88 | ``` 89 | #### Reset database 90 | ``` 91 | heroku pg:reset DATABASE 92 | heroku run rake db:migrate 93 | heroku run rake db:seed 94 | ``` 95 | 96 | ### Set up and Running 97 | ``` 98 | npm install webpack --save 99 | npm install react --save 100 | npm install react-dom --save 101 | npm install flux --save 102 | npm install babel-core --save 103 | npm install babel-loader --save 104 | npm install babel-preset-react --save 105 | npm install react-router --save 106 | ``` 107 | -------------------------------------------------------------------------------- /doc/stack_and_queue.md: -------------------------------------------------------------------------------- 1 | # Stack and Queue 2 | Stack and queue can be implemented with just an array but, 3 | * Stack can only push and pop 4 | * Queue can only push and shift 5 | 6 | ## Implementation 7 | ``` ruby 8 | class Stack 9 | def initialize 10 | @store = [] 11 | end 12 | 13 | def push(value) 14 | @store << value 15 | end 16 | 17 | def pop 18 | @store.pop 19 | end 20 | end 21 | 22 | class Queue 23 | def initialize 24 | @store = [] 25 | end 26 | 27 | def enqueue(value) 28 | @store << value 29 | end 30 | 31 | def dequeue 32 | @store.shfit 33 | end 34 | end 35 | ``` 36 | 37 | ## Interview Problem 38 | __Stack Min__ - How would you design a stack which, in addition to push and pop, 39 | has a function `min` which returns the minimum element? Write a class for it 40 | ``` ruby 41 | class MinStack 42 | def initialize 43 | @store = [] 44 | end 45 | 46 | def pop 47 | raise "index out of bound" if @store.empty? 48 | @store.pop 49 | end 50 | 51 | def push(value) 52 | if @store.empty? 53 | @store << {value: value, min: value} 54 | else 55 | @store << {value: value, min: [@store.last[:min], value].min} 56 | end 57 | end 58 | 59 | def min 60 | return nil if @store.empty? 61 | @store.last[:min] 62 | end 63 | end 64 | ``` 65 | 66 | __Stack of Plates__: Imagine a stack of plates. If the stack gets too high, 67 | it might topple. Therefore, in real life, we would likely start a new stack 68 | when the previous stack exceeds some threshold. Implement a data structure 69 | `SetOfStacks` that mimics this. `SetOfStacks` should be composed of several 70 | stacks and should create a new stack once the previous one exceeds capacity. 71 | `SetOfStacks.push()` and `SetOfStacks.pop()` should behave identically to 72 | a single stack. 73 | Follow-up: Implement a function popAt(idx) which performs a pop operation 74 | on a specific sub-stack. 75 | 76 | ``` ruby 77 | class SetOfStacks 78 | def initialize(cap = 10) 79 | @cap = cap 80 | @store = [] 81 | end 82 | 83 | def push(val) 84 | if @store.empty? || @store.last.length == @cap 85 | @store << [val] 86 | else 87 | @store.last << val 88 | end 89 | end 90 | 91 | def pop(val) 92 | raise "index out of bound" if @store.empty? 93 | if @store.last.empty? 94 | @store.pop 95 | @store.last.pop 96 | else 97 | @store.last.pop 98 | end 99 | end 100 | 101 | def popAt(idx) 102 | @store[idx].pop 103 | end 104 | end 105 | ``` 106 | 107 | __Queue via Stack__: Implement a `MyQueue` class which implements a queue 108 | using two stacks. 109 | ``` ruby 110 | # This is basically a stack queue 111 | class MyQueue 112 | def initialize 113 | @in, @out = [], [] 114 | end 115 | 116 | def enqueue(value) 117 | @in << value 118 | end 119 | 120 | def dequeue 121 | if @out.empty? 122 | @out << @in.pop until @in.empty? 123 | end 124 | @out.pop 125 | end 126 | end 127 | ``` 128 | 129 | __Balance Braces__: Write a function that check whether a string has 130 | balancing parenthesis, brackets, and curly braces. For example, [](){}, [(){}], 131 | [{()}] are balanced. [(]), )()], {}[)](), are not balanced. 132 | 133 | ``` ruby 134 | def isBalanced?(string) 135 | match = {'(' => ')', '{' => '}', '[' => ']'} 136 | return false if string.length % 2 == 1 137 | stack = [] 138 | string.chars.each do |char| 139 | if stack.empty? 140 | stack << char 141 | else 142 | if match[stack.last] == char 143 | stack.pop 144 | else 145 | stack << char 146 | end 147 | end 148 | end 149 | stack.empty? ? true : false 150 | end 151 | ``` 152 | -------------------------------------------------------------------------------- /doc/stack_queue.md: -------------------------------------------------------------------------------- 1 | # StackQueue 2 | 3 | StackQueue is a queue that is implemented with stack. The sole purpose of making 4 | this data structure is to solve the max window range problem. 5 | 6 | ## Implementation 7 | ``` ruby 8 | class StackQueue 9 | def initialize 10 | @in, @out = [], [] 11 | end 12 | 13 | def enqueue(value) 14 | @in << value 15 | end 16 | 17 | def dequeue 18 | if @out.empty? 19 | @out << @in.pop until @in.empty? 20 | end 21 | @out.pop 22 | end 23 | end 24 | ``` 25 | We can modify the stack to be a MinMaxStack, so when elements are pushed into the stack, it has the ability to keep track of the min and max. Using MinMaxStack, we can create a queue, a sliding window, to keep track of the min and max of all the numbers inside the window. 26 | 27 | ``` ruby 28 | class MinMaxStack 29 | def initialize 30 | @store = [] 31 | end 32 | 33 | def length 34 | @store.length 35 | end 36 | 37 | def push(value) 38 | if @store.empty? 39 | @store << { value: value, min: value, max: value } 40 | else 41 | @store << { 42 | value: value, 43 | max: [@store.last[:max], value].max, 44 | min: [@store.last[:min], value].min 45 | } 46 | end 47 | end 48 | 49 | def pop 50 | (@store.pop)[:value] 51 | end 52 | 53 | def max 54 | @store.empty? ? nil : (@store.last)[:max] 55 | end 56 | 57 | def min 58 | @store.empty? ? nil : (@store.last)[:min] 59 | end 60 | end 61 | 62 | class MinMaxStackQueue 63 | def initialize 64 | @in, @out = MinMaxStack.new, MinMaxStack.new 65 | end 66 | 67 | def enqueue(value) 68 | @in.push(value) 69 | end 70 | 71 | def dequeue 72 | if @out.length == 0 73 | @out.push(@in.pop) until @in.length == 0 74 | end 75 | @out.pop 76 | end 77 | 78 | def length 79 | @in.length + @out.length 80 | end 81 | 82 | def max 83 | maxes = [] 84 | maxes << @in.max if @in.length > 0 85 | maxes << @out.max if @out.length > 0 86 | maxes.max 87 | end 88 | 89 | def min 90 | mins = [] 91 | mins << @in.min if @in.length > 0 92 | mins << @out.min if @out.length > 0 93 | mins.min 94 | end 95 | end 96 | ``` 97 | ## Interview Problems 98 | Windowed Max Range - Given an array, and a window size `w`, find the maximum 99 | `max - min` within a range of `w` elements 100 | 101 | ``` ruby 102 | windowed_max_range([1, 0, 2, 5, 4, 8], 3) == 5 # => 0, 2, 5 103 | windowed_max_range([1, 3, 2, 5, 4, 8], 5) == 6 # => 3, 2, 5, 4, 8 104 | ``` 105 | There is a naive solution to this problem, which takes O(n^2) time. It 106 | considers all the subarrays of size w. Using MinMaxStackQueue, we can 107 | turn this problem into O(n). 108 | 109 | ``` ruby 110 | def windowed_max_range(array, window_size) 111 | max_range = nil 112 | queue = MinMaxStackQueue.new 113 | array.each do |el| 114 | queue.enqueue(el) 115 | if max_range.nil? || (queue.max - queue.min) > max_range 116 | max_range = (queue.max - queue.min) 117 | end 118 | 119 | if queue.length == window_size 120 | queue.dequeue 121 | end 122 | end 123 | max_range 124 | end 125 | ``` 126 | -------------------------------------------------------------------------------- /doc/suffix_tree.md: -------------------------------------------------------------------------------- 1 | # Suffix Tree 2 | A suffix tree is simply a trie of all the proper suffixes of a string S. 3 | 4 | For example: 5 | 6 | | Word | Suffixes | 7 | |-------|----------| 8 | | hello | hello | 9 | | | ello | 10 | | | llo | 11 | | | lo | 12 | | | o | 13 | 14 | Naturally, some suffixes can become the prefix of other suffix, for example. 15 | 16 | | Word | Suffixes | 17 | |-------|----------| 18 | | abcbc | abcbc | 19 | | | bcbc | 20 | | | cbc | 21 | | | bc | 22 | | | c | 23 | 24 | Now the problem is that `bc` is a prefix of `bcbc` but `bc` is a valid suffix. This will terminate search in the suffix tree prematurely. So we need a solution. We need to add a terminal character `$` to the end of the string. 25 | 26 | `$` is a character that does not appear elsewhere in the string, and we define it to be less than other characters (e.g. for DNA: $ < A < C < G < T) 27 | 28 | `$` enforces a rule we are all used to using, e.g. "as" comes before "ash" in the dictionary 29 | 30 | `$` guarantees no suffix is a prefix of any other suffix, because `bc$` is not a prefix of `bcbc$` 31 | 32 | ## Constructing a Suffix Tree Naively 33 | Suppose we have a string T = "abaaba" 34 | 35 | What are the suffixes? 36 | 37 | | Word | Suffixes | 38 | |--------|-----------| 39 | | abaaba | abaaba$ | 40 | | | baaba$ | 41 | | | aaba$ | 42 | | | aba$ | 43 | | | ba$ | 44 | | | a$ | 45 | | | $ | 46 | 47 | We should expect 6 leave nodes that each contains a valid suffix for the word 48 | 49 | ### The Trie of Suffixes 50 | We can either store the letters in edges or in nodes. If we store the letters in nodes, then we define keys to be letters and values to be null or the suffix. 51 | 52 | This is a suffix tree 53 | 54 | ![suffix_tree] 55 | [suffix_tree]: ../img/suffix_tree.png 56 | Green squares are the leaf nodes 57 | -------------------------------------------------------------------------------- /doc/system_design_interview.md: -------------------------------------------------------------------------------- 1 | # System Design Interview 2 | ## Examples 3 | * Design a URL shortening service like bit.ly 4 | * Implement Google Search 5 | * Design a client-server application which allows people to play chess with one another 6 | * How would you store relations in a social network like Facebook and implement a feature 7 | where one user receives notifications when their friends like the same thing as they do? 8 | 9 | ## System Design Process 10 | ### Step 1 Constraints and use cases 11 | The very first thing to do with any system design question is to clarify the system's 12 | constraints and to identify what use cases the system needs to satisfy. Spend a few 13 | minutes questioning your interviewer and agreeing on the scope of the system. 14 | 15 | For example, the URL shortening service could be meant to be serve just a few 16 | thousand users, but each could be sharing millions of URLs. It could be meant to 17 | handle millions of clicks on the shortened URLs or dozens. The service may have 18 | to provide extensive statistics about each shortened URL. 19 | 20 | #### Use Cases: 21 | 1. Shortening: take an url and return a much shorter url 22 | 2. Redirection: take an short url and re-direct to the original url 23 | 3. Custom url: allow user to create their own shortened url 24 | 4. High availability of the system 25 | 26 | #### Out of Scope: 27 | 4. Analytics 28 | 5. Automatic link expiration 29 | 6. Manual link removal 30 | 7. UI vs API 31 | 32 | #### Constraints: 33 | Large percentage of URL shortening usage is driven by tweets. There are 1.5 billion new tweets per month. We can make some assumptions or at least estimation. We know that shortening request is much less frequent than re-redirecting request. Not every tweet has some links in it. For a safe number, let's assume we get 1 billion requests per month. 34 | 35 | 1. One billion requests per month 36 | 2. Traffic: 10% from shortening and 90% from redirection 37 | 3. 100 million new urls per month 38 | 4. 900 million redirections per month 39 | 5. Requests per second: 400+ 40 | 6. 6 billion URLs in 5 years 41 | 7. 500 bytes per URL 42 | 8. 6 bytes per hash 43 | 44 | So we will have 3000 billion bytes for URL, 3TB for all urls and 36 GB for all hashes (over 5 years) 45 | 46 | Write per second: 40*(500 + 6) = 20 KB/s 47 | 48 | Read per second: 360*506 bytes = 180KB/s 49 | 50 | ### Step 2 Abstract design 51 | Once you've scoped the system you're about to design, you should continue by outlining 52 | a high-level abstract design. The goal is to outline all the important components that the 53 | architecture will need. 54 | 55 | 1. Application service layer: (serves the requests) 56 | * Shortening service 57 | * Redirection service 58 | 2. Data storage layer (keeps track of the hash => url mappings) 59 | * Acts like a big hash table 60 | * Stores new mapping and retrieves a value given a key 61 | 62 | ### Step 3 Understanding bottlenecks 63 | Most likely your high level design will have one or more bottlenecks given the 64 | constraints of the problem. You are not expected to design a system from ground up, 65 | which immediately handles all the load in the world. It just needs to be scalable. 66 | 67 | Now that you have your high level design, start thinking about what bottlenecks it has. 68 | Perhaps your system needs a load balancer and many machines behind it to handle the user 69 | requests. Or maybe the data is so huge that you need to distribute your database 70 | on multiple machines. 71 | 72 | #### Bottlenecks: 73 | * Traffic is not going to be hard 74 | * Data is the only concern, we need to index directly into the url we need. 75 | 76 | ### Step 4 Scaling your abstract design 77 | Time to make it scale! 78 | 79 | #### Everything is a trade off 80 | This is the one of the most fundamental concepts in system design. 81 | 82 | There rarely is one perfect way to do things. Each company ends up with 83 | a different architecture. Designing a scalable system is an optimization task; 84 | there are tons of constraints (time, budget, knowledge, complexity, technologies currently 85 | available, etc...) Every technology, every pattern is great for somethings, and not 86 | so great for others. Understanding these props and cons, the advtanges 87 | and disadvantages, is the key. 88 | 89 | Being able to understand and discuss these trade-offs is what system design 90 | is all about. 91 | 92 | Going back to the URL shortening example: 93 | 94 | Scalable Design: 95 | 1. Application Service Layer 96 | * Start with one machine 97 | * Add a load balancer and a cluster of machines over time 98 | * Usually there's spike in traffic. We can activate the cluster when 99 | we experience a spike in traffic but then we turn it off when the spike 100 | is gone 101 | * We need to add redundancy because we expect high availability 102 | 103 | 2. Data Storage Layer 104 | * We have the following requirements: 105 | * Billion of objects 106 | * Each object is fairly small 107 | * There are no relationships between the objects 108 | * Reads are 9x more frequent than writes 109 | * 3TB of urls, 36GB of hashes 110 | * MySQL: 111 | * Widely used 112 | * Mature technology 113 | * Clear scaling paradigms (sharding, master/slave replication, master/master replication) 114 | * Used by Facebook, Twitter, Google, and etc... 115 | * Index lookups are very fast 116 | * Approach and Design: 117 | * Mapping 118 | * hash: VARCHAR(6) - we only need 6 characters for the shortened URL 119 | * original_url: VARCHAR(512) 120 | * Create an unique index on the hash (36GB+) and we want to call it in memory 121 | * We have two options 122 | 1. Vertical scaling: increase the memory on the data server. 123 | 2. Horizontal scaling: partition the data into multiple disk spaces. It's good to have a 124 | good partition strategy early on. As the site gets more popular, it will be easy 125 | to add more machines to scale horizontally. 126 | * In case this skyrockets so hard, we need to do master-slave replications. We will 127 | direct read to slaves and direct writes to master 128 | * Strategy: 129 | * Start off with vertical scaling, just buy more hardware and stack it on 130 | * Eventually, we will hit the ceiling, we then partition the data by taking 131 | the first character of the shortened url and converted it to ascii code, mod it 132 | by the number of partitions, put it there. (Perhaps we will need consistent hashing technique soon) 133 | * Then we will need to seek a master-slave setup at the very end. 134 | -------------------------------------------------------------------------------- /doc/topological_sort.md: -------------------------------------------------------------------------------- 1 | # Topological Sort & Directed Graphs 2 | 3 | ## Directed Graphs API 4 | * `#initialize(num_of_vertices)` - create an empty diagraph with numbers of 5 | vertices 6 | * `#addEdge(v, w)` - connect two nodes/vertices together with direction, 7 | from v -> w 8 | * `#adjList(v)` - return vertices pointing from v 9 | * `#v_count` - return number of vertices 10 | * `#e_count` - return number of edges 11 | 12 | ## DFS Topological Sort 13 | Coming soon... 14 | 15 | ## Kahn's Algorithm 16 | A zero degree vertex is a vertex that has no dependency. It's like taking 17 | classes in college. If a course has no pre-requisite, it has zero degree of 18 | dependency. The algorithm works by calculating the number of dependency for 19 | every single vertex and store them in a hash map. If a vertex/node has no 20 | dependency, it gets push into the queue. 21 | 22 | Once the queue starts running, every "completed" or visited node will get 23 | push into the sorted list, as a way of saying we will complete these tasks 24 | first. Then the algorithm looks at the out edges of the node and decrement 25 | their dependency because we have completed one pre-requisite, so one less 26 | requirement/dependency. Repeat the process and eventually everything will 27 | be sorted topologically. 28 | 29 | ``` ruby 30 | def topological_sort(vertices) 31 | sorted_vertices = [] 32 | zero_in_degree_queue = [] 33 | in_edge_count = Hash.new 34 | 35 | # O(|V|) 36 | vertices.each do |vertex| 37 | in_edge_count[vertex] = vertex.in_edges.length 38 | if vertex.in_edges.length == 0 39 | zero_in_degree_queue << vertex 40 | end 41 | end 42 | 43 | # O(|V| + |E|) 44 | # goes through all the nodes and edges 45 | while zero_in_degree_queue.length > 0 46 | node = zero_in_degree_queue.shift 47 | node.out_edges.each do |edge| 48 | neighbor = edge.to_vertex 49 | # edge.destroy! 50 | in_edge_count[neighbor] -= 1 51 | if in_edge_count[neighbor] == 0 52 | zero_in_degree_queue << neighbor 53 | end 54 | end 55 | sorted_vertices << node 56 | end 57 | sorted_vertices 58 | end 59 | ``` 60 | ## Strongly Connected 61 | The idea of strongly connected component is unique to digraphs. A single 62 | directed connection between two vertices is not enough to constitute a 63 | strong connection, that makes a connection (or connected component) but not 64 | a strongly connected component 65 | 66 | Definition: *v* and *w* are strongly connected if there is a directed path 67 | from *v* to *v* and a directed path 68 | 69 | ![strongly_connected][scc] 70 | [scc]: ../img/strongly_connected.png 71 | 72 | A-B-C are strongly connected components because A can get to B, B can get to A, B can get to C and C can get to B, and etc... 73 | 74 | However, A-B-C can get to D-E-F-G but D-E-F-G can't get back to A-B-C so they are not strongly connected. 75 | 76 | ## Kosaraju-Sharir Algorithm 77 | * Reverse graph: strong components in G are the same as in G reversed 78 | * Kernal DAG: contract each strong component into a single vertex 79 | * Idea: 80 | * compute topological order (reverse postorder) in kernel DAG 81 | * run DFS, considering vertices in reverse topological order 82 | 83 | Phase 1: run DFS on reversed G to compute reverse postorder 84 | 85 | Phase 2: run DFS on original G, considering vertices in order given by 86 | first DFS from Phase 1. 87 | -------------------------------------------------------------------------------- /doc/tree_traversal.md: -------------------------------------------------------------------------------- 1 | # Tree Traversal 2 | There are three types depth-first of tree traversal 3 | * Pre-order 4 | * In-order 5 | * Post-order 6 | 7 | While breadth-first traversal is just level order (using a queue) 8 | 9 | Using a binary search tree as example, the traversals can be described as 10 | * Pre-order => [root] [left] [right] 11 | * In-order => [left] [root] [right] 12 | * Post-order => [left] [right] [root] 13 | 14 | Using binary search tree as an example 15 | 16 | ![binary_tree][binary_tree] 17 | [binary_tree]: ../img/binary_tree.png 18 | 19 | ## Pre-order 20 | ``` ruby 21 | def BinarySearchTree.preorder!(node) 22 | return [] unless node 23 | arr = [node.value] 24 | arr += BinarySearchTree.preorder!(node.left) 25 | arr += BinarySearchTree.preorder!(node.right) 26 | arr 27 | end 28 | ``` 29 | 1. First call will hit the root, 5 30 | 2. We have 5(left)(right) 31 | 3. Next call will be root of the left subtree which is 4 32 | 4. 5(4(left)(right))(right) 33 | 5. Keep going, hit the root of the left subtree of 4. 34 | 6. Then we have 5(4(2(left)(right))(right)))(right) 35 | 7. The subtrees of 2 are 1 and 3. 36 | 8. 5(4(213)(right))(right), right subtree of 4 is empty so we have 54213(right) 37 | 9. Hit the root of the right subtree, that is 8. We have 54213(8(left)(right)) 38 | 10. 54213(8(76)(910)) 39 | 11. Final answer: 54213876910 40 | 41 | ## In-order 42 | ``` ruby 43 | def BinarySearchTree.inorder!(node) 44 | return [] unless node 45 | arr = [] 46 | arr += BinarySearchTree.inorder!(node.left) 47 | arr << node.value 48 | arr += BinarySearchTree.inorder!(node.right) 49 | arr 50 | end 51 | ``` 52 | We will use similar process here to show that in-order traversal for a binary search tree will yield sorted output. 53 | 54 | 1. First call stack leads to (left)5(right) 55 | 2. The left subtree of 5 is (left)4(empty) 56 | 3. ((left)4)5(right) 57 | 4. The left subtree of 4 will give 123 58 | 5. (1234)5(right) 59 | 6. The right subtree of 5 is (left)8(right) 60 | 7. Left subtree of 8 will give 67 61 | 8. Right subtree of 8 will give 910 62 | 9. (1234)5(678910) 63 | 10. Final answer: 12345678910 64 | 65 | ## Post-order 66 | ``` ruby 67 | def BinarySearchTree.postorder!(node) 68 | return [] unless node 69 | arr = [] 70 | arr += BinarySearchTree.postorder!(node.left) 71 | arr += BinarySearchTree.postorder!(node.right) 72 | arr << node.value 73 | arr 74 | end 75 | ``` 76 | Just as above, 77 | 78 | 1. (left)(right)5 79 | 2. ((left)(empty)4)(right)5 80 | 3. ((132)(empty)4)(right)5 81 | 4. 1324((left)(right)8)5 82 | 5. 1324((67)(109)8)5 83 | 6. 13246710985 84 | -------------------------------------------------------------------------------- /doc/trie.md: -------------------------------------------------------------------------------- 1 | # Trie 2 | A data structure for searching with string keys 3 | 4 | ## Speed Comparison 5 | L is the length of string 6 | 7 | | implementation | search | insert | delete | operations on keys | 8 | | -------------- | ---------- | ------ | ------ | ------------------ | 9 | | red-black BST | L + log(N) | log(N) | log(N) | compareTo() | 10 | | hash table | L | L | L | equals() & hash() | 11 | 12 | Can we do better? Yes if we can avoid examining the entire key, as with 13 | string sorting. Trie provides a data structure that enables one to search 14 | without going through the entire length of the string, especially when 15 | the key doesn't exist in the library. 16 | 17 | It is faster than hashing, more flexible than BST, for string specifically 18 | A trie could be a good data structure for building a memory-efficient 19 | dictionary with fast lookups and auto-completion.Think of it as a hash table, 20 | providing fast lookup of key-value pairs (or just lookup of keys), but 21 | unlike a hash table it allows you to iterate over the keys in sorted order. 22 | 23 | ## R-way Trie 24 | * Store characters into nodes (not keys) 25 | * Each node has up to R children, R stands for radix. 26 | * Example: for lowercase letters, R = 26, so each node can have up to 26 27 | children 28 | * Store values in nodes corresponding to last character in the key 29 | * Example: "calvin" => {age: 24}, the age hash will be stored in node "n" 30 | 31 | ### Search in a Trie 32 | Follow links corresponding to each character in the key 33 | * Search hit: node where search ends has a non-null value 34 | * Search miss: reach null link or node where search ends has null value 35 | 36 | ### Insertion in Trie 37 | Follow links corresponding to each character in the key 38 | * Encounter a null link: create new node 39 | * Encounter the last character of the key: set value in that node 40 | 41 | ### Deletion in Trie 42 | * Find the node corresponding to key and set value to null 43 | * If node has null value and all null links, remove that node and recurse 44 | 45 | ## Implementation 46 | ``` ruby 47 | # This is very similar to binary search tree, in terms of implementation 48 | class Trie 49 | def initialize 50 | @root = TrieNode.new 51 | end 52 | 53 | def insert(key, value) 54 | Trie.insert!(@root, key, value) 55 | end 56 | 57 | def find(key) 58 | Trie.find!(@root, key) 59 | end 60 | 61 | def delete(key) 62 | Trie.delete!(@root, key) 63 | end 64 | 65 | def self.insert!(node,key,value) 66 | return nil if key.nil? 67 | if key.length == 0 68 | node.value = value 69 | else 70 | node.children[key[0]] = TrieNode.new(node, nil, key[0]) unless node.children[key[0]] 71 | Trie.insert!(node.children[key[0]], key[1..-1], value) 72 | end 73 | end 74 | 75 | def self.find!(node, key) 76 | return nil if node.nil? 77 | if key.length == 0 78 | node.value 79 | else 80 | Trie.find!(node.children[key[0]], key[1..-1]) 81 | end 82 | end 83 | 84 | def self.delete!(node, key) 85 | return nil if node.nil? 86 | if key.length == 0 87 | node.value = nil 88 | Trie.clean_up_nodes!(node) 89 | else 90 | Trie.delete!(node.children[key[0]], key[1..-1]) 91 | end 92 | end 93 | 94 | def self.clean_up_nodes!(node) 95 | curr_node = node.parent 96 | while curr_node.children.keys == 1 97 | curr_node.children = Hash.new 98 | latest_letter = curr_node.letter 99 | curr_node = curr_node.parent 100 | end 101 | curr_node.children[latest_letter] = nil 102 | end 103 | end 104 | ``` 105 | -------------------------------------------------------------------------------- /img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/.DS_Store -------------------------------------------------------------------------------- /img/archi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/archi.png -------------------------------------------------------------------------------- /img/asyn_system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/asyn_system.png -------------------------------------------------------------------------------- /img/binary_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/binary_tree.png -------------------------------------------------------------------------------- /img/dijkstra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/dijkstra.png -------------------------------------------------------------------------------- /img/finite_state_automata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/finite_state_automata.png -------------------------------------------------------------------------------- /img/gfs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/gfs.png -------------------------------------------------------------------------------- /img/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/graph.png -------------------------------------------------------------------------------- /img/heapify_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/heapify_up.png -------------------------------------------------------------------------------- /img/heroku-postgre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/heroku-postgre.png -------------------------------------------------------------------------------- /img/load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/load.png -------------------------------------------------------------------------------- /img/partition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/partition.png -------------------------------------------------------------------------------- /img/strongly_connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/strongly_connected.png -------------------------------------------------------------------------------- /img/suffix_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinfeng/study-guide/15838484a6201830420338813b6fbc64aeb663a3/img/suffix_tree.png -------------------------------------------------------------------------------- /run-node.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var cache = {}; 3 | function dynamicFib(n) { 4 | if (n <= 0) { 5 | return []; 6 | } else if (n === 1) { 7 | return [1]; 8 | } else if (n === 2) { 9 | return [1, 1]; 10 | } 11 | 12 | if (cache[n]) { 13 | return cache[n]; 14 | } else { 15 | cache[n] = dynamicFib(n - 1).concat(dynamicFib(n - 1)[dynamicFib(n - 1).length - 1] + 16 | dynamicFib(n - 2)[dynamicFib(n - 2).length - 1]); 17 | return cache[n]; 18 | } 19 | } 20 | 21 | function fib(n) { 22 | if (n <= 0) { 23 | return []; 24 | } else if (n === 1) { 25 | return [1]; 26 | } else if (n === 2) { 27 | return [1, 1]; 28 | } 29 | return fib(n - 1).concat(fib(n - 1)[fib(n - 1).length - 1] + 30 | fib(n - 2)[fib(n - 2).length - 1]); 31 | } 32 | 33 | function stringSort(strArr) { 34 | for (var d = strArr[0].length - 1; d >= 0; d -= 1) { 35 | strArr = mergeSort(strArr, d); 36 | } 37 | return strArr; 38 | } 39 | 40 | function mergeSort(arr, i) { 41 | if (arr.length <= 1) { 42 | return arr; 43 | } else { 44 | var mid = Math.floor(arr.length/2); 45 | var left = arr.slice(0, mid); 46 | var right = arr.slice(mid, arr.length); 47 | return merge(mergeSort(left), mergeSort(right), i); 48 | } 49 | } 50 | 51 | function merge(left, right, i) { 52 | var result = []; 53 | while (left.length !== 0 && right.length !== 0) { 54 | if (left[0].charCodeAt(i) <= right[0].charCodeAt(i)) { 55 | result.push(left.shift()); 56 | } else { 57 | result.push(right.shift()); 58 | } 59 | } 60 | if (left.length === 0) { 61 | return result.concat(right); 62 | } else { 63 | return result.concat(left); 64 | } 65 | } 66 | 67 | // var strArr = ["abc", "xyz", "opq", "rst", "uvw", "efg"]; 68 | // console.log(stringSort(strArr)); 69 | 70 | function iterativeDFS(root, target) { 71 | let stack = [root]; 72 | while (stack.length > 0) { 73 | let currentNode = stack.pop(); 74 | if (currentNode.value === target) { 75 | return currentNode; 76 | } else { 77 | let children = currentNode.children; 78 | for (let i = children.length - 1; i >= 0; i--) { 79 | stack.push(children[i]); 80 | } 81 | } 82 | } 83 | return null; 84 | } 85 | 86 | function breadthFirst(root, target) { 87 | let queue = [root]; 88 | while (queue.length > 0) { 89 | let currentNode = queue.shift(); 90 | if (currentNode.value === target) { 91 | return currentNode; 92 | } else { 93 | let children = currentNode.children; 94 | for (let i = 0; i < children.length; i++) { 95 | queue.push(children[i]); 96 | } 97 | } 98 | } 99 | return null; 100 | } 101 | 102 | class TreeNode { 103 | constructor(value) { 104 | this.value = value; 105 | this.children = []; 106 | } 107 | } 108 | 109 | let root = new TreeNode(0); 110 | let a = new TreeNode(1.1); 111 | let b = new TreeNode(1.2); 112 | let c = new TreeNode(1.3); 113 | let d = new TreeNode(2.1); 114 | let e = new TreeNode(2.2); 115 | let f = new TreeNode(2.3); 116 | 117 | root.children.push(a,b,c); 118 | a.children.push(d, e); 119 | b.children.push(f); 120 | 121 | console.log(iterativeDFS(root, 4)); 122 | console.log(breadthFirst(root, 4)); 123 | 124 | // Frontend Angular JS 125 | // MySQL 126 | // Java 127 | // 7~8 People on a team 128 | // secure 129 | // testable 130 | // rgonugunta@clearslide.com 131 | -------------------------------------------------------------------------------- /run-ruby.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | require 'byebug' 3 | 4 | def fibs(n) 5 | return [] if n <= 0 6 | return [1] if n == 1 7 | return [1, 1] if n == 2 8 | fibs(n-1) + [fibs(n-1).last + fibs(n-2).last] 9 | end 10 | 11 | $record = Hash.new 12 | def fibs_dynamic(n) 13 | return [] if n <= 0 14 | return [1] if n == 1 15 | return [1, 1] if n == 2 16 | return $record[n] if $record[n] 17 | $record[n] = fibs_dynamic(n-1) + [fibs_dynamic(n-1).last + fibs_dynamic(n-2).last] 18 | $record[n] 19 | end 20 | 21 | def fibonacci(n) 22 | return 0 if n == 0 23 | return 1 if n == 1 24 | return fibonacci(n - 1) + fibonacci(n - 2) 25 | end 26 | 27 | def fibonacci_dynamic(n) 28 | return 0 if n == 0 29 | a, b = 0, 1 30 | i = 2 31 | while (i < n) 32 | c = a + b 33 | a = b 34 | b = c 35 | i += 1 36 | end 37 | a + b 38 | end 39 | # puts Benchmark.measure {fibonacci_dynamic(35)} 40 | # puts Benchmark.measure {fibonacci(35)} 41 | # puts Benchmark.measure { p fibs_dynamic(35) } 42 | # puts Benchmark.measure { p fibs(35)} 43 | def minimal_insert(bstree, arr) 44 | if arr.length == 1 45 | #bstree.insert(arr[0]) 46 | bstree << arr[0].to_s 47 | elsif arr.length == 0 48 | # do nothing 49 | else 50 | mid_idx = arr.length/2 51 | #bstree.insert(arr[mid_idx]) 52 | bstree << arr[mid_idx].to_s 53 | # Recurse on left sub-array 54 | minimal_insert(bstree, arr.take(mid_idx)) 55 | # Recurse on right sub-array 56 | minimal_insert(bstree, arr.drop(mid_idx + 1)) 57 | end 58 | end 59 | 60 | # str = "" 61 | # minimal_insert(str, [1,2,3,4,5,6,7]) 62 | 63 | # O(k nlog(n)) time, with each string has length k 64 | def string_sort(str_arr) 65 | (str_arr.first.length - 1).downto(0) do |d| 66 | str_arr = merge_sort(str_arr, d) 67 | end 68 | str_arr 69 | end 70 | 71 | def merge_sort(arr, idx) 72 | return arr if arr.empty? || arr.length == 1 73 | mid_idx = arr.length/2 74 | left_arr = merge_sort(arr.take(mid_idx), idx) 75 | right_arr = merge_sort(arr.drop(mid_idx), idx) 76 | merge(left_arr, right_arr, idx) 77 | end 78 | 79 | def merge(left, right, i) 80 | sorted_result = [] 81 | until left.empty? || right.empty? 82 | if left[0][i].ord <= right[0][i].ord 83 | sorted_result << left.shift 84 | else 85 | sorted_result << right.shift 86 | end 87 | end 88 | if left.empty? 89 | sorted_result += right 90 | else 91 | sorted_result += left 92 | end 93 | sorted_result 94 | end 95 | 96 | def triple_step_dynamic(n, memo = Hash.new) 97 | return [[]] if n == 0 98 | return [[1]] if n == 1 99 | return [[1,1],[2]] if n == 2 100 | 101 | result = [] 102 | memo[n - 1] = triple_step_dynamic(n - 1, memo) unless memo[n - 1] 103 | result += memo[n - 1].map do |combo| 104 | combo + [1] 105 | end 106 | 107 | memo[n - 2] = triple_step_dynamic(n - 2, memo) unless memo[n - 2] 108 | result += memo[n - 2].map do |combo| 109 | combo + [2] 110 | end 111 | 112 | memo[n - 3] = triple_step_dynamic(n - 3, memo) unless memo[n - 3] 113 | result += memo[n - 3].map do |combo| 114 | combo + [3] 115 | end 116 | result 117 | end 118 | 119 | def triple_step(n) 120 | return [[]] if n <= 0 121 | return [[1]] if n == 1 122 | return [[1,1], [2]] if n == 2 123 | result = [] 124 | result += triple_step(n - 1).map do |combo| 125 | combo + [1] 126 | end 127 | result += triple_step(n - 2).map do |combo| 128 | combo + [2] 129 | end 130 | result += triple_step(n - 3).map do |combo| 131 | combo + [3] 132 | end 133 | result 134 | end 135 | 136 | # puts Benchmark.measure {triple_step(25)} 137 | # puts Benchmark.measure {triple_step_dynamic(25)} 138 | 139 | def find_magic_index(arr, start_idx, end_idx) 140 | return nil if end_idx < start_idx 141 | mid = (start_idx + end_idx)/2 142 | if arr[mid] == mid 143 | mid 144 | elsif arr[mid] > mid 145 | find_magic_index(arr, start, mid - 1) 146 | else 147 | find_magic_index(arr, mid + 1, end_idx) 148 | end 149 | end 150 | 151 | def make_change(total, m, coins) 152 | return 0 if total < 0 || m < 0 153 | return 1 if total == 0 154 | return make_change(total, m - 1, coins) + make_change(total - coins[m], m, coins) 155 | end 156 | 157 | p make_change(100, 3, [1,5,10,25]) 158 | 159 | def isBalanced?(string) 160 | match = {'(' => ')', '{' => '}', '[' => ']'} 161 | return false if string.length % 2 == 1 162 | stack = [] 163 | string.chars.each do |char| 164 | if stack.empty? 165 | stack << char 166 | else 167 | if match[stack.last] == char 168 | stack.pop 169 | else 170 | stack << char 171 | end 172 | end 173 | end 174 | stack.empty? ? true : false 175 | end 176 | --------------------------------------------------------------------------------