diff --git a/README.md b/README.md index 939f0fa3..665bc09c 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,22 @@ Collection of interview questions with Unit Tests. Problems includes Data Struct - [Implement Queue Using Stack](src/_DataStructures_/Stack/immitate-queue-using-stack) - [Baseball Game](src/_DataStructures_/Stack/baseball-game) - - [Minimum Stack](src/_DataStructures_/Stack/min-stack) + - [Find minimum in the Stack](src/_DataStructures_/Stack/min-stack) - [Balanced Parenthesis](src/_DataStructures_/Stack/balanced-parenthesis) - [Postfix Expression Evaluation](src/_DataStructures_/Stack/postfix-expression-evaluation) - [Remove Consecutive Repeated Digits](src/_DataStructures_/Stack/remove-consecutive-repeated-digits) - [Implement 2 Stacks using Single Array](src/_DataStructures_/Stack/2-stacks-using1-array) - - [Queue](src/_DataStructures_/Queue) + - [Weave](src/_DataStructures_/Queue/weave) +- [Doubly Linked List](src/_DataStructures_/DoublyLinkedList) + +- [Trees](src/_DataStructures_/Trees) + - [Binary Search Tree](src/_DataStructures_/Trees/BST) + - [Suffix Tree](src/_DataStructures_/SuffixTree) + ### Logical Problems - [Anagrams](src/_Problems_/anagrams) @@ -60,6 +66,10 @@ Collection of interview questions with Unit Tests. Problems includes Data Struct - [Binary Search](src/_Searching_/BinarySearch) +### Algorithms + +- [LRU Cache](src/_Algorithms_/lru-cache) + ### Path Finder - [A\*](src/PathFinder/AStart) @@ -69,8 +79,6 @@ Collection of interview questions with Unit Tests. Problems includes Data Struct - [Caeser Cipher](src/_Classics_/caeser_cipher) - [Fibonacci](src/_Classics_/fibonacci) ---- - ## CONTRIBUTION Guide It's great to know that you want to contribute to this repo. Thanks for taking interest. Before you start, read the following carefully. diff --git a/src/_Algorithms_/lru-cache/index.js b/src/_Algorithms_/lru-cache/index.js new file mode 100644 index 00000000..beb96347 --- /dev/null +++ b/src/_Algorithms_/lru-cache/index.js @@ -0,0 +1,71 @@ +/* +Least recently used (LRU) - cache implementation + +get(key) – Get the value (will always be positive) of the key if the key exists in the cache, otherwise return false. +Complexity: O(1) + +set(key, value) – Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item. +Complexity: O(1) +*/ + +const DoublyLinkedList = require('../../_DataStructures_/DoublyLinkedList/index'); + +class LRUCache { + constructor(n) { + this.size = n; + this.map = new Map(); + this.list = new DoublyLinkedList(); + } + + // this method will work in O(1) + set(key, value) { + const data = { + key, + value, + }; + if (!this.map.has(key)) { + this.list.addAtBeginning(data); + this.map.set(key, this.list.head.next); + + if (this.list.length() > this.size) { + const lastNode = this.list.tail.previous.data; + this.map.delete(lastNode.key); + this.list.removeAtEnd(); + } + } else { + this.list.remove(this.map.get(key)); + this.list.addAtBeginning(data); + this.map.set(key, this.list.head.next); + } + } + + // this method will work in O(1) + get(key) { + if (this.map.has(key)) { + const node = this.map.get(key); + const { value } = node.data; + this.list.remove(node); + this.list.addAtBeginning({ + key, + value, + }); + this.map.set(key, this.list.head.next); + } + return false; + } +} + +// const lru = new LRUCache(3); +// lru.set(1, 1); +// lru.set(2, 2); +// lru.set(3, 3); +// lru.set(4, 4); +// lru.set(5, 5); +// lru.set(2, 2); +// lru.get(5, 5); +// lru.list.display(); + + +module.exports = { + LRUCache, +}; diff --git a/src/_Classics_/fibonacci/index.js b/src/_Classics_/fibonacci/index.js index 6c61bdc1..979fb5c8 100644 --- a/src/_Classics_/fibonacci/index.js +++ b/src/_Classics_/fibonacci/index.js @@ -2,8 +2,10 @@ // the algorithm has time complexity of O(n^2), very bad! function fibonacci(position) { // if position is 1 or 2, the number in fibonacci sequence will be 1 - if (position <= 1) { + if (position === 1 || position === 0) { return position; + } else if (position < 0) { + throw new Error('Invalid Position'); } // else the element in fibonacci sequence will be the sum of @@ -26,8 +28,11 @@ function fibonacciMemoized(index, cache) { if (cache[index]) { return cache[index]; } else { - if (index <=1) { + if (index === 1 || index === 0) { return index; + } else if (index < 0) { + throw new Error('Invalid Position'); + } else { cache[index] = fibonacciMemoized(index - 1, cache) + @@ -43,8 +48,10 @@ function fibonacciMemoized(index, cache) { function fibonacciTabular(n) { const table = [0, 1]; - if (n <= 1) { + if (n === 1 || n === 0) { return n; + } else if (n < 0) { + throw new Error('Invalid Position'); } for (let i = 2; i <= n; i += 1) { table[i] = table[i - 1] + table[i - 2]; diff --git a/src/_DataStructures_/DoublyLinkedList/index.js b/src/_DataStructures_/DoublyLinkedList/index.js new file mode 100644 index 00000000..522924b9 --- /dev/null +++ b/src/_DataStructures_/DoublyLinkedList/index.js @@ -0,0 +1,65 @@ +/* eslint-disable class-methods-use-this */ +class Node { + constructor(data, previous, next) { + this.data = data; + this.previous = previous; + this.next = next; + } +} + +class DoublyLinkedList { + constructor() { + // head -> tail + // head <- tail + this.head = new Node(null, null, null); + this.tail = new Node(null, null, null); + this.head.next = this.tail; // head next point to tail + this.tail.previous = this.head; // tail previous point to head + this.size = 0; + } + + addAtBeginning(value) { + const newNode = new Node(value, this.head, this.head.next); + this.head.next.previous = newNode; + this.head.next = newNode; + this.size += 1; + } + + addAtEnd(value) { + const newNode = new Node(value, this.tail.previous, this.tail); + this.tail.previous.next = newNode; + this.tail.previous = newNode; + this.size += 1; + } + + removeAtBeginning() { + this.remove(this.head.next); + this.size -= 1; + } + + removeAtEnd() { + this.remove(this.tail.previous); + this.size -= 1; + } + + remove(node) { + const previousNode = node.previous; + const nextNode = node.next; + previousNode.next = nextNode; + nextNode.previous = previousNode; + } + + length() { + return this.size; + } + + display() { + let address = this.head.next; + while (address !== this.tail) { + console.log(address.data); + address = address.next; + } + } +} + +module.exports = DoublyLinkedList; diff --git a/src/_DataStructures_/LinkedList/loop-in-list/index.js b/src/_DataStructures_/LinkedList/loop-in-list/index.js index 26749191..849604b2 100644 --- a/src/_DataStructures_/LinkedList/loop-in-list/index.js +++ b/src/_DataStructures_/LinkedList/loop-in-list/index.js @@ -1,6 +1,6 @@ // Floyd’s Cycle-Finding Algorithm -function detechLoop(linkedList) { +function detectLoop(linkedList) { let slow = linkedList.getFirst(); let fast = linkedList.getFirst(); @@ -14,3 +14,7 @@ function detechLoop(linkedList) { } return false; } + +module.exports = { + detectLoop, +}; diff --git a/src/_DataStructures_/LinkedList/loop-in-list/loop-in-list.test.js b/src/_DataStructures_/LinkedList/loop-in-list/loop-in-list.test.js new file mode 100644 index 00000000..7f7afeca --- /dev/null +++ b/src/_DataStructures_/LinkedList/loop-in-list/loop-in-list.test.js @@ -0,0 +1,40 @@ +const { LinkedList } = require('../index'); +const { detectLoop } = require('.'); + +describe('Loop a LinkedList', () => { + let loopList = null; + let last = null; + beforeEach(() => { + loopList = new LinkedList(); + loopList.addAtBeginning('1'); + loopList.addAtEnd('2'); + loopList.addAtEnd('3'); + loopList.addAtEnd('4'); + loopList.addAtEnd('5'); + // Create loop in list + last = loopList.getLast(); + last.next = loopList.getFirst(); + }); + + it('Should break for empty list', () => { + loopList.delete(); + expect(() => detectLoop(loopList)).toThrow(TypeError); + }); + + it('Should return `true` when looping list', () => { + expect(detectLoop(loopList)).toEqual(true); + }); + + it('Should return `false` for non loop list', () => { + last.next = null; // remove loop in list + expect(detectLoop(loopList)).toEqual(false); + }); + + it('Should return `false` for non loop list', () => { + const list = new LinkedList(); + list.addAtBeginning('1'); + list.addAtEnd('1'); + list.addAtEnd('1'); + expect(detectLoop(list)).toEqual(false); + }); +}); diff --git a/src/_DataStructures_/Trees/BST/Node.js b/src/_DataStructures_/Trees/BST/Node.js new file mode 100644 index 00000000..2b515b2a --- /dev/null +++ b/src/_DataStructures_/Trees/BST/Node.js @@ -0,0 +1,7 @@ +module.exports = class Node { + constructor(value) { + this.value = value; + this.leftChild = null; // will be a node + this.rightChild = null; // will be a node + } +}; diff --git a/src/_DataStructures_/Trees/BST/index.js b/src/_DataStructures_/Trees/BST/index.js new file mode 100644 index 00000000..0a1ad377 --- /dev/null +++ b/src/_DataStructures_/Trees/BST/index.js @@ -0,0 +1,110 @@ +const Node = require('./Node'); + +class BinarySearchTree { + constructor(value) { + this.root = new Node(value); + } + + insert(root, value) { + if (root === null) { + const newNode = new Node(value); + // eslint-disable-next-line no-param-reassign + root = newNode; + return root; + } + + if (value < root.value) { + // eslint-disable-next-line no-param-reassign + root.leftChild = this.insert(root.leftChild, value); + return root; + } + if (value > root.value) { + // eslint-disable-next-line no-param-reassign + root.rightChild = this.insert(root.rightChild, value); + return root; + } + return root; + } + + preorder(root) { + /** returning an array so as to make testing easy */ + let arr = []; + if (root === null) return []; + arr.push(root.value); + + const left = this.preorder(root.leftChild); + arr = [...arr, ...left]; + + const right = this.preorder(root.rightChild); + arr = [...arr, ...right]; + return arr; + } + + inorder(root) { + /** left - root - right */ + if (root === null) return []; + let arr = []; + const left = this.inorder(root.leftChild); + arr = [...left, ...arr]; + + // print root + arr = [...arr, root.value]; + + const right = this.inorder(root.rightChild); + arr = [...arr, ...right]; + return arr; + } + + postorder(root) { + /** left - right - root */ + + if (root === null) return []; + let arr = []; + + const left = this.postorder(root.leftChild); + arr = [...left, ...arr]; + + const right = this.postorder(root.rightChild); + arr = [...arr, ...right]; + + return [...arr, root.value]; + } + + search(root, value) { + if (root === null) return false; + if (value === root.value) return true; + + if (value < root.value) { + return this.search(root.leftChild, value); + } + if (value > root.value) { + return this.search(root.rightChild, value); + } + return false; + } +} + +// const bst = new BinarySearchTree(6); +// console.log(bst.root); +// bst.insert(bst.root, 4); +// bst.insert(bst.root, 9); +// bst.insert(bst.root, 2); +// bst.insert(bst.root, 5); +// bst.insert(bst.root, 8); +// bst.insert(bst.root, 12); + +// console.log(bst.root); + +// const preorder = bst.preorder(bst.root); +// console.log('Preorder Traversal - ', preorder); + +// const inorder = bst.inorder(bst.root); +// console.log('Inorder Traversal - ', inorder); + +// const postorder = bst.postorder(bst.root); +// console.log('Postorder Traversal - ', postorder); + +// const search = 18; +// console.log(`Search for ${search}`, bst.search(bst.root, search)); + +module.exports = BinarySearchTree; diff --git a/src/_DataStructures_/Trees/SuffixTree/index.js b/src/_DataStructures_/Trees/SuffixTree/index.js new file mode 100644 index 00000000..3a24308d --- /dev/null +++ b/src/_DataStructures_/Trees/SuffixTree/index.js @@ -0,0 +1,121 @@ +/* eslint-disable no-plusplus */ +/* +Implemented by watching this conceptually video: https://www.youtube.com/watch?v=VA9m_l6LpwI + +Suffix for banana are : +banana +anana +nana +ana +na +a + +Constructing a suffix tree is O(n*d) where d is length of max string + +Searching a suffix of a string is O(d) where d is length of suffix string. +If found then return the index, else return -1 + +*/ + +class Node { + constructor(value, isEnd, index) { + this.data = value; + this.isEnd = isEnd; + this.index = index; + this.next = new Map(); + } +} + +class SuffixTree { + constructor(string) { + this.head = new Node(); + this.string = string; + } + + constructSuffixTree() { + const { string } = this; + let currentString = ''; + for (let i = string.length - 1; i >= 0; i -= 1) { + currentString = string[i] + currentString; + let j = 0; + let currentNode = this.head; + while (j < currentString.length) { + if (!currentNode.next.has(currentString[j])) { + currentNode.next.set(currentString[j], new Node(currentString, true, i)); + break; + } else { + let k = 0; + const partialMatchNode = currentNode.next.get(currentString[j]); + const partialMatchString = partialMatchNode.data; + + let matchString = ''; + while (k < partialMatchString.length && j < currentString.length && partialMatchString[k] === currentString[j]) { + matchString += currentString[j]; + k++; + j++; + } + + let diffString = ''; + while (k < partialMatchString.length) { + diffString += partialMatchString[k]; + k++; + } + partialMatchNode.data = matchString; + if (diffString) { + partialMatchNode.next.set(diffString[0], new Node(diffString, true, partialMatchNode.index)); + partialMatchNode.isEnd = false; + partialMatchNode.index = null; + } + + if (partialMatchNode.next.has(currentString[j])) { + currentNode = partialMatchNode; + } else { + let nextString = ''; + while (j < currentString.length) { + nextString += currentString[j]; + j++; + } + partialMatchNode.next.set(nextString[0], new Node(nextString, true, i)); + break; + } + } + } + } + } + + findSubstring(string) { + if (!this.head.next.has(string[0])) { + return -1; + } + + let currentNode = this.head.next.get(string[0]); + let currentNodeValue = currentNode.data; + + let i = 0; let j = 0; + + while (i < string.length) { + j = 0; + while (i < string.length && j < currentNodeValue.length && string[i++] === currentNodeValue[j++]); + + if (i === string.length && j === currentNodeValue.length && currentNode.isEnd) { + return currentNode.index; + } + + if (currentNode.next.has(string[i])) { + currentNode = currentNode.next.get(string[i]); + currentNodeValue = currentNode.data; + } else { + return -1; + } + } + return -1; + } +} + +// const s = new SuffixTree('banana'); +// s.constructSuffixTree(); + +// console.log(s.findSubstring('nana')); + + +module.exports = SuffixTree; diff --git a/src/_Problems_/get-smallest-common-number/get-smallest-common-number.test.js b/src/_Problems_/get-smallest-common-number/get-smallest-common-number.test.js new file mode 100644 index 00000000..a5436caa --- /dev/null +++ b/src/_Problems_/get-smallest-common-number/get-smallest-common-number.test.js @@ -0,0 +1,31 @@ +const { getSmallestCommonNumber } = require('.'); + +describe('Get common smallest number between two integer arrays', () => { + it('Should return -1 when both has empty array', () => { + const arr1 = []; + const arr2 = []; + + expect(getSmallestCommonNumber(arr1, arr2)).toEqual(-1); + }); + + it('Should return -1 when no common between two integer arrays', () => { + const arr1 = [1, 3, 5]; + const arr2 = [2, 4, 6]; + + expect(getSmallestCommonNumber(arr1, arr2)).toEqual(-1); + }); + + it('Should return common smallest number between unsorted two integer arrays', () => { + const arr1 = [-10, 3]; + const arr2 = [2, -10, 7]; + + expect(getSmallestCommonNumber(arr1, arr2)).toEqual(-10); + }); + + it('Should return common smallest number between sorted two integer arrays', () => { + const arr1 = [2, 3]; + const arr2 = [2, 5, 7]; + + expect(getSmallestCommonNumber(arr1, arr2)).toEqual(2); + }); +});