├── .gitignore ├── .prettierrc ├── README.md ├── package-lock.json ├── package.json └── src ├── algoexpert └── easy │ ├── binarySearch.js │ ├── branchSums.js │ ├── branchSums.test.js │ ├── classPhotos.js │ ├── depthFirstSearch.js │ ├── evaluateExpressionTree.js │ ├── findClosestValueInBST.js │ ├── findClosestValueInBST.test.js │ ├── isPalindrome.js │ ├── middleNode.js │ ├── minimumWaitingTime.js │ ├── nodeDepths.js │ ├── nodeDepths.test.js │ ├── nonConstructibleChange.js │ ├── nonConstructibleChange.test.js │ ├── sortedSquaredArray.js │ ├── sortedSquaredArray.test.js │ ├── tandemBicycle.js │ ├── tournamentWinner.js │ ├── tournamentWinner.test.js │ ├── transposeMatrix.js │ ├── transposeMatrix.test.js │ ├── twoNumberSum.js │ ├── twoNumberSum.test.js │ ├── validateSubsequence.js │ └── validateSubsequence.test.js ├── arrays ├── README.md ├── index.js └── prefixSum.js ├── dynamic-programming ├── README.md ├── fibMemoization.js ├── learning-2-dimensional.js ├── minCostClimbingStairs.js ├── minCostPath.js ├── minSubArraySum.js ├── minSubArraySum.test.js └── tribonacci.js ├── graphs ├── README.md ├── adj-list-directed.js ├── adj-list-undirected.js ├── adj-list-undirected.test.js ├── adj-list-weighted.js ├── adj-list-weighted.test.js ├── adjacency-matrix.js ├── adjacency-matrix.test.js └── ajd-list-directed.test.js ├── hash-tables ├── README.md ├── index.js ├── index.test.js └── linked-list.js ├── heap ├── README.md ├── max-heap.js ├── max-heap.test.js ├── min-heap.js └── min-heap.test.js ├── interviews ├── isRealEmail.js └── pickAvatarBackgroundColor.js ├── leetcode ├── arrays │ ├── arrayStringsAreEqual.js │ ├── binarySearch.js │ ├── characterReplacement.js │ ├── concatenationOfArray.js │ ├── containsNearbyDuplicate.js │ ├── findDifference.js │ ├── findDuplicate.js │ ├── insertionSort.js │ ├── isBadVersion.js │ ├── isMonotonic.js │ ├── isPalindrome.js │ ├── kthLargestElement.js │ ├── lengthOfLongestSubstring.js │ ├── maxArea.js │ ├── maxSubArray.js │ ├── maxSubarraySumCircular.js │ ├── maxTurbulenceSize.js │ ├── mergeSort.js │ ├── minEatingSpeed.js │ ├── minSubArrayLen.js │ ├── numOfSubarrays.js │ ├── pivotIndex.js │ ├── productExceptSelf.js │ ├── quickSort.js │ ├── removeDuplicates-two.js │ ├── removeDuplicates.js │ ├── searchMatrix.js │ ├── sortColors.js │ ├── sumRange.js │ └── sumRegion.js ├── backtracking │ ├── combinationSum.js │ ├── hasPathSum.js │ └── subsets.js ├── bitwise │ └── hammingWeight.js ├── dynamic-programming │ ├── fib.js │ ├── houseRobber.js │ ├── longestCommonSubsequence.js │ ├── uniquePaths.js │ └── uniquePathsWithObstacles.js ├── graphs │ ├── canFinish.js │ ├── cloneGraph.js │ ├── maxAreaOfIsland.js │ ├── numIslands.js │ ├── orangesRotting.js │ └── shortestPathBinaryMatrix.js ├── hash │ ├── LRUCache.js │ ├── containsDuplicate.js │ ├── hashMap.js │ ├── hashSet.js │ └── twoSum.js ├── heap │ ├── kClosest.js │ ├── kthLargest.js │ └── lastStoneWeight.js ├── linked-lists │ ├── BrowserHistory.js │ ├── MyLinkedList.js │ ├── MyLinkedList.test.js │ ├── detectCycle.js │ ├── hasCycle.js │ ├── mergedTwoLists.js │ ├── middleNode.js │ ├── pairSum.js │ └── reverseList.js ├── stack │ └── calPoints.js └── trees │ ├── WordDictionary.js │ ├── buildTree.js │ ├── deleteNode.js │ ├── inorderTraversal.js │ ├── insertIntoBST.js │ ├── levelOrder.js │ ├── rightSideView.js │ └── searchBST.js ├── linked-lists ├── README.md ├── circular-linked-lists.js ├── circular-linked-lists.test.js ├── detect-cycle.js ├── detect-cycle.test.js ├── doubly.js ├── doubly.test.js ├── merge-two-sorted.js ├── merged-two-sorted.test.js ├── reverse-singly.js ├── reverse-singly.test.js ├── singly.js └── singly.test.js ├── queues ├── MinPriorityQueue.js ├── MinPriorityQueue.test.js ├── README.md ├── RegularQueue.js └── RegularQueue.test.js ├── recursion ├── README.md ├── countOccurrences.js ├── countOccurrences.test.js ├── factorial.js ├── factorial.test.js ├── fibonacci.js ├── hasAdjacentDuplicates.js ├── hasAdjacentDuplicates.test.js ├── reverseString.js ├── reverseString.test.js ├── sumOfDigits.js ├── sumOfDigits.test.js ├── ways-to-climb-stairs.js └── ways-to-climb-stairs.test.js ├── stacks ├── README.md ├── index.js └── index.test.js ├── trees ├── README.md ├── avl-tree.js ├── avl-tree.test.js ├── binary-search-tree.js ├── binary-search-tree.test.js ├── binary-tree.js └── binary-tree.test.js └── trie ├── index.js └── index.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "embeddedLanguageFormatting": "auto", 5 | "htmlWhitespaceSensitivity": "css", 6 | "insertPragma": false, 7 | "jsxBracketSameLine": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 80, 10 | "proseWrap": "preserve", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "semi": false, 14 | "singleQuote": true, 15 | "tabWidth": 2, 16 | "trailingComma": "es5", 17 | "useTabs": false, 18 | "vueIndentScriptAndStyle": false 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ds-algo-js", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "vitest" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "nodemon": "^3.0.2", 14 | "vitest": "^1.1.1" 15 | }, 16 | "dependencies": { 17 | "@datastructures-js/priority-queue": "^6.3.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/algoexpert/easy/binarySearch.js: -------------------------------------------------------------------------------- 1 | function binarySearch(array, target) { 2 | let leftPointer = 0 3 | let rightPointer = array.length - 1 4 | 5 | while (leftPointer <= rightPointer) { 6 | const mid = Math.floor((leftPointer + rightPointer) / 2) 7 | 8 | if (target === array[mid]) { 9 | return mid 10 | } 11 | 12 | if (target > array[mid]) { 13 | leftPointer = mid + 1 14 | } else { 15 | rightPointer = mid - 1 16 | } 17 | } 18 | 19 | return -1 20 | } 21 | -------------------------------------------------------------------------------- /src/algoexpert/easy/branchSums.js: -------------------------------------------------------------------------------- 1 | export function branchSums(root, sumOfBranch = 0, sums = []) { 2 | sumOfBranch += root.value 3 | 4 | const isAtLeafNode = !root.left && !root.right 5 | if (isAtLeafNode) { 6 | sums.push(sumOfBranch) 7 | } 8 | 9 | if (root.left) { 10 | branchSums(root.left, sumOfBranch, sums) 11 | } 12 | 13 | if (root.right) { 14 | branchSums(root.right, sumOfBranch, sums) 15 | } 16 | 17 | return sums 18 | } 19 | -------------------------------------------------------------------------------- /src/algoexpert/easy/branchSums.test.js: -------------------------------------------------------------------------------- 1 | import { branchSums } from './branchSums' 2 | import { it, expect } from 'vitest' 3 | 4 | class BST { 5 | constructor(value) { 6 | this.value = value 7 | this.left = null 8 | this.right = null 9 | } 10 | } 11 | 12 | it('calculates branch sums for a binary tree', () => { 13 | const bst = new BST(1) 14 | bst.left = new BST(2) 15 | bst.right = new BST(3) 16 | bst.left.left = new BST(4) 17 | bst.left.right = new BST(5) 18 | bst.right.left = new BST(6) 19 | bst.right.right = new BST(7) 20 | const sums = branchSums(bst) 21 | expect(sums).toEqual([7, 8, 10, 11]) 22 | }) 23 | 24 | it('calculates branch sums for a binary tree with single branch', () => { 25 | const bst = new BST(1) 26 | bst.left = new BST(2) 27 | bst.left.left = new BST(3) 28 | const sums = branchSums(bst) 29 | expect(sums).toEqual([6]) 30 | }) 31 | 32 | it('calculates branch sums for a binary tree with single node', () => { 33 | const bst = new BST(1) 34 | const sums = branchSums(bst) 35 | expect(sums).toEqual([1]) 36 | }) 37 | -------------------------------------------------------------------------------- /src/algoexpert/easy/classPhotos.js: -------------------------------------------------------------------------------- 1 | function classPhotos(redShirtHeights, blueShirtHeights) { 2 | redShirtHeights.sort((a, b) => b - a) 3 | blueShirtHeights.sort((a, b) => b - a) 4 | 5 | const shirtColorInFirstRow = 6 | redShirtHeights[0] > blueShirtHeights[0] ? 'RED' : 'BLUE' 7 | 8 | for (let i = 0; i < redShirtHeights.length; i++) { 9 | if (shirtColorInFirstRow === 'RED') { 10 | if (redShirtHeights[i] <= blueShirtHeights[i]) { 11 | return false 12 | } 13 | } else { 14 | if (blueShirtHeights[i] <= redShirtHeights[i]) { 15 | return false 16 | } 17 | } 18 | } 19 | 20 | return true 21 | } 22 | -------------------------------------------------------------------------------- /src/algoexpert/easy/depthFirstSearch.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(name) { 3 | this.name = name 4 | this.children = [] 5 | } 6 | 7 | addChild(name) { 8 | this.children.push(new Node(name)) 9 | return this 10 | } 11 | 12 | depthFirstSearch(array, node = this) { 13 | array.push(node.name) 14 | 15 | for (const child of node.children) { 16 | this.depthFirstSearch(array, child) 17 | } 18 | 19 | return array 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/algoexpert/easy/evaluateExpressionTree.js: -------------------------------------------------------------------------------- 1 | function evaluateExpressionTree(tree) { 2 | if (!tree) { 3 | return 0 4 | } 5 | 6 | if (!tree.left && !tree.right) { 7 | return tree.value 8 | } 9 | 10 | const leftValue = evaluateExpressionTree(tree.left) 11 | const rightValue = evaluateExpressionTree(tree.right) 12 | 13 | switch (tree.value) { 14 | case -1: 15 | return leftValue + rightValue 16 | case -2: 17 | return leftValue - rightValue 18 | case -3: 19 | return Math.trunc(leftValue / rightValue) 20 | default: 21 | return Math.trunc(leftValue * rightValue) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/algoexpert/easy/findClosestValueInBST.js: -------------------------------------------------------------------------------- 1 | export function findClosestValueInBst(tree, target) { 2 | return findClosestValueInBstHelper(tree, target, tree.value) 3 | } 4 | 5 | function findClosestValueInBstHelper(tree, target, closest) { 6 | if (tree === null) { 7 | return closest 8 | } 9 | 10 | let diffClosest = Math.abs(target - closest) 11 | let diffTreeValue = Math.abs(target - tree.value) 12 | 13 | if (diffClosest > diffTreeValue) { 14 | closest = tree.value 15 | } 16 | 17 | if (target < tree.value) { 18 | return findClosestValueInBstHelper(tree.left, target, closest) 19 | } else if (target > tree.value) { 20 | return findClosestValueInBstHelper(tree.right, target, closest) 21 | } else { 22 | return closest 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/algoexpert/easy/findClosestValueInBST.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { findClosestValueInBst } from './findClosestValueInBST' 3 | 4 | class BST { 5 | constructor(value) { 6 | this.value = value 7 | this.left = null 8 | this.right = null 9 | } 10 | } 11 | 12 | it('finds closest value in BST', () => { 13 | const bst = new BST(10) 14 | bst.left = new BST(5) 15 | bst.right = new BST(15) 16 | bst.left.left = new BST(2) 17 | bst.left.right = new BST(5) 18 | bst.right.left = new BST(13) 19 | bst.right.right = new BST(22) 20 | bst.right.left.right = new BST(14) 21 | const target = 12 22 | const closest = findClosestValueInBst(bst, target) 23 | expect(closest).toBe(13) 24 | }) 25 | 26 | it('finds closest value in BST when target is equal to a node', () => { 27 | const bst = new BST(10) 28 | bst.left = new BST(5) 29 | bst.right = new BST(15) 30 | const target = 15 31 | const closest = findClosestValueInBst(bst, target) 32 | expect(closest).toBe(15) 33 | }) 34 | 35 | it('finds closest value in BST when BST has only one node', () => { 36 | const bst = new BST(10) 37 | const target = 15 38 | const closest = findClosestValueInBst(bst, target) 39 | expect(closest).toBe(10) 40 | }) 41 | -------------------------------------------------------------------------------- /src/algoexpert/easy/isPalindrome.js: -------------------------------------------------------------------------------- 1 | // function inefficientIsPalindrome(string) { 2 | // let filteredString = '' 3 | // for (let i = 0; i < string.length; i++) { 4 | // if (string[i].match(/[a-zA-Z0-9]/)) { 5 | // filteredString += string[i].toLowerCase() 6 | // } 7 | // } 8 | // let reversedString = filteredString.split('').reverse().join('') 9 | // return filteredString === reversedString 10 | // } 11 | 12 | function isCharacterAlphaNumeric(character) { 13 | const charCode = character.charCodeAt(0) 14 | const isDigit = charCode > 47 && charCode < 58 // numeric (0-9) 15 | const isUpperCase = charCode > 64 && charCode < 91 // upper alpha (A-Z) 16 | const isLowerCase = charCode > 96 && charCode < 123 // lower alpha (a-z) 17 | 18 | return isDigit || isUpperCase || isLowerCase 19 | } 20 | 21 | function isStringPalindrome(inputString) { 22 | let startPointer = 0 23 | let endPointer = inputString.length - 1 24 | 25 | while (startPointer < endPointer) { 26 | // Move startPointer to the next alphanumeric character from the start 27 | while ( 28 | startPointer < endPointer && 29 | !isCharacterAlphaNumeric(inputString[startPointer]) 30 | ) { 31 | startPointer++ 32 | } 33 | 34 | // Move endPointer to the next alphanumeric character from the end 35 | while ( 36 | startPointer < endPointer && 37 | !isCharacterAlphaNumeric(inputString[endPointer]) 38 | ) { 39 | endPointer-- 40 | } 41 | 42 | // Compare characters ignoring case 43 | if ( 44 | inputString[startPointer].toLowerCase() !== 45 | inputString[endPointer].toLowerCase() 46 | ) { 47 | return false 48 | } 49 | 50 | startPointer++ 51 | endPointer-- 52 | } 53 | return true 54 | } 55 | -------------------------------------------------------------------------------- /src/algoexpert/easy/middleNode.js: -------------------------------------------------------------------------------- 1 | function middleNode(linkedList) { 2 | let current = linkedList 3 | let jumper = linkedList 4 | 5 | while (jumper && jumper.next) { 6 | current = current.next 7 | jumper = jumper.next.next 8 | } 9 | 10 | return current 11 | } 12 | -------------------------------------------------------------------------------- /src/algoexpert/easy/minimumWaitingTime.js: -------------------------------------------------------------------------------- 1 | function minimumWaitingTime(queries) { 2 | queries.sort((a, b) => a - b) 3 | 4 | let totalTime = 0 5 | 6 | for (let i = 0; i < queries.length; i++) { 7 | totalTime += queries[i] * (queries.length - (i + 1)) 8 | } 9 | 10 | return totalTime 11 | } 12 | -------------------------------------------------------------------------------- /src/algoexpert/easy/nodeDepths.js: -------------------------------------------------------------------------------- 1 | export function nodeDepths(root, level = 0) { 2 | if (!root) { 3 | return 0 4 | } 5 | 6 | const totalDepth = 7 | nodeDepths(root.left, level + 1) + nodeDepths(root.right, level + 1) 8 | 9 | return level + totalDepth 10 | } 11 | -------------------------------------------------------------------------------- /src/algoexpert/easy/nodeDepths.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { nodeDepths } from './nodeDepths' 3 | 4 | it('returns correct depth for a single node tree', () => { 5 | const tree = { value: 1, left: null, right: null } 6 | expect(nodeDepths(tree)).toBe(0) 7 | }) 8 | 9 | it('returns correct depth for a two level tree', () => { 10 | const tree = { 11 | value: 1, 12 | left: { value: 2, left: null, right: null }, 13 | right: { value: 3, left: null, right: null }, 14 | } 15 | expect(nodeDepths(tree)).toBe(2) 16 | }) 17 | -------------------------------------------------------------------------------- /src/algoexpert/easy/nonConstructibleChange.js: -------------------------------------------------------------------------------- 1 | export function nonConstructibleChange(coins) { 2 | if (!coins.length) return 1 3 | 4 | coins.sort((a, b) => a - b) 5 | let minChange = 0 6 | 7 | for (let i = 0; i < coins.length; i++) { 8 | if (coins[i] > minChange + 1) break 9 | 10 | minChange += coins[i] 11 | } 12 | 13 | return minChange + 1 14 | } 15 | -------------------------------------------------------------------------------- /src/algoexpert/easy/nonConstructibleChange.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { nonConstructibleChange } from './nonConstructibleChange' 3 | 4 | it('should return the minimum amount of change that you cannot create', () => { 5 | const coins = [5, 7, 1, 1, 2, 3, 22] 6 | 7 | expect(nonConstructibleChange(coins)).toEqual(20) 8 | }) 9 | -------------------------------------------------------------------------------- /src/algoexpert/easy/sortedSquaredArray.js: -------------------------------------------------------------------------------- 1 | export function sortedSquaredArray(array) { 2 | let leftPointer = 0 3 | let rightPointer = array.length - 1 4 | 5 | const newArr = [] 6 | 7 | while (leftPointer !== rightPointer) { 8 | const leftNum = array[leftPointer] 9 | const rightNum = array[rightPointer] 10 | 11 | if (Math.pow(leftNum, 2) > Math.pow(rightNum, 2)) { 12 | newArr.unshift(leftNum * leftNum) 13 | leftPointer++ 14 | } else { 15 | newArr.unshift(rightNum * rightNum) 16 | rightPointer-- 17 | } 18 | } 19 | 20 | newArr.unshift(array[leftPointer] * array[leftPointer]) 21 | 22 | return newArr 23 | } 24 | -------------------------------------------------------------------------------- /src/algoexpert/easy/sortedSquaredArray.test.js: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | import { sortedSquaredArray } from './sortedSquaredArray' 3 | 4 | it('should return an array with 6 values', () => { 5 | const array = [1, 2, 3, 5, 6, 8] 6 | 7 | expect(sortedSquaredArray(array)).toEqual([1, 4, 9, 25, 36, 64]) 8 | }) 9 | 10 | it('should return an array with 8 values, even if they contain negative numbers', () => { 11 | const array = [-10, -5, 0, 5, 10] 12 | 13 | expect(sortedSquaredArray(array)).toEqual([0, 25, 25, 100, 100]) 14 | }) 15 | -------------------------------------------------------------------------------- /src/algoexpert/easy/tandemBicycle.js: -------------------------------------------------------------------------------- 1 | function tandemBicycle(redShirtSpeeds, blueShirtSpeeds, fastest) { 2 | redShirtSpeeds.sort((a, b) => b - a) 3 | blueShirtSpeeds.sort((a, b) => (fastest ? a - b : b - a)) 4 | 5 | let result = 0 6 | 7 | for (let i = 0; i < redShirtSpeeds.length; i++) { 8 | result += Math.max(redShirtSpeeds[i], blueShirtSpeeds[i]) 9 | } 10 | 11 | return result 12 | } 13 | -------------------------------------------------------------------------------- /src/algoexpert/easy/tournamentWinner.js: -------------------------------------------------------------------------------- 1 | export function tournamentWinner(competitions, results) { 2 | let index = 0 3 | 4 | let hashTable = {} 5 | 6 | let highestValue = '' 7 | 8 | while (index !== competitions.length) { 9 | const winningTeam = competitions[index][1 - results[index]] 10 | 11 | if (!hashTable[winningTeam]) { 12 | hashTable[winningTeam] = 3 13 | 14 | if (!hashTable[highestValue]) { 15 | highestValue = winningTeam 16 | } 17 | } else { 18 | const newValue = hashTable[winningTeam] + 3 19 | 20 | if (hashTable[highestValue] < newValue) { 21 | highestValue = winningTeam 22 | } 23 | 24 | hashTable[winningTeam] = newValue 25 | } 26 | 27 | index++ 28 | } 29 | 30 | return highestValue 31 | } 32 | -------------------------------------------------------------------------------- /src/algoexpert/easy/tournamentWinner.test.js: -------------------------------------------------------------------------------- 1 | import { tournamentWinner } from './tournamentWinner' 2 | import { expect, it } from 'vitest' 3 | 4 | it('should return the team with the highest score', () => { 5 | const competitions = [ 6 | ['HTML', 'C#'], 7 | ['C#', 'Python'], 8 | ['Python', 'HTML'], 9 | ] 10 | 11 | const results = [0, 0, 1] 12 | 13 | expect(tournamentWinner(competitions, results)).toEqual('Python') 14 | }) 15 | -------------------------------------------------------------------------------- /src/algoexpert/easy/transposeMatrix.js: -------------------------------------------------------------------------------- 1 | export const transposeMatrix = (matrix) => { 2 | let rows = matrix.length 3 | let columns = matrix[0].length 4 | 5 | const newArr = [] 6 | 7 | for (let column = 0; column < columns; column++) { 8 | newArr[column] = [] // Initialize each sub-array 9 | for (let row = 0; row < rows; row++) { 10 | newArr[column][row] = matrix[row][column] 11 | } 12 | } 13 | 14 | return newArr 15 | } 16 | -------------------------------------------------------------------------------- /src/algoexpert/easy/transposeMatrix.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { transposeMatrix } from './transposeMatrix' 3 | 4 | it('transposeMatrix', () => { 5 | const matrix = [ 6 | [1, 2, 3], 7 | [4, 5, 6], 8 | ] 9 | 10 | const expected = [ 11 | [1, 4], 12 | [2, 5], 13 | [3, 6], 14 | ] 15 | 16 | expect(transposeMatrix(matrix)).toEqual(expected) 17 | }) 18 | -------------------------------------------------------------------------------- /src/algoexpert/easy/twoNumberSum.js: -------------------------------------------------------------------------------- 1 | export function twoNumberSum(array, targetSum) { 2 | let numSet = new Set() 3 | 4 | for (let i = 0; i < array.length; i++) { 5 | const num = array[i] 6 | 7 | const targetNum = targetSum - num 8 | 9 | if (numSet.has(targetNum)) { 10 | return [num, targetNum] 11 | } 12 | 13 | numSet.add(num) 14 | } 15 | 16 | return [] 17 | } 18 | -------------------------------------------------------------------------------- /src/algoexpert/easy/twoNumberSum.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { twoNumberSum } from './twoNumberSum' 3 | 4 | it('should return an empty array if no two numbers sum to the target sum', () => { 5 | const array = [1, 2, 3, 4, 5, 6, 7, 8, 9] 6 | const targetSum = 100 7 | 8 | expect(twoNumberSum(array, targetSum)).toEqual([]) 9 | }) 10 | 11 | it('should return an empty array if the input array has less than two numbers', () => { 12 | const array = [1] 13 | const targetSum = 100 14 | 15 | expect(twoNumberSum(array, targetSum)).toEqual([]) 16 | }) 17 | 18 | it.only('should return two values that sum to the target sum', () => { 19 | const array = [3, 5, -4, 8, 11, 1, -1, 6] 20 | const targetSum = 10 21 | 22 | expect(twoNumberSum(array, targetSum)).toEqual([-1, 11]) 23 | }) 24 | -------------------------------------------------------------------------------- /src/algoexpert/easy/validateSubsequence.js: -------------------------------------------------------------------------------- 1 | export function isValidSubsequence(array, sequence) { 2 | let sequencePointer = 0 3 | let arrayPointer = 0 4 | 5 | while (arrayPointer !== array.length) { 6 | if (array[arrayPointer] === sequence[sequencePointer]) { 7 | sequencePointer++ 8 | } 9 | 10 | arrayPointer++ 11 | } 12 | 13 | return sequencePointer === sequence.length ? true : false 14 | } 15 | -------------------------------------------------------------------------------- /src/algoexpert/easy/validateSubsequence.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { isValidSubsequence } from './validateSubsequence' 3 | 4 | it('is valid subsequence with numbers', () => { 5 | // true 6 | const array = [5, 1, 22, 25, 6, -1, 8, 10] 7 | const sequence = [1, 6, -1, 10] 8 | 9 | expect(isValidSubsequence(array, sequence)).toEqual(true) 10 | 11 | // false 12 | const array2 = [5, 1, 22, 25, 6, -1, 8, 10] 13 | const sequence2 = [5, 1, 22, 25, 6, -1, 8, 10, 12] 14 | expect(isValidSubsequence(array2, sequence2)).toEqual(false) 15 | }) 16 | -------------------------------------------------------------------------------- /src/arrays/README.md: -------------------------------------------------------------------------------- 1 | # Arrays 2 | 3 | There are static and dynamic arrays. 4 | 5 | Arrays are a collection of items. They are stored in contiguous memory locations. Meaning, they are stored one after the other in memory. 6 | 7 | Different systems take different amount of bytes for a single number. For example, a 32-bit system takes 4 bytes for a number. A 64-bit system takes 8 bytes for a number. 8 | 9 | So, for example in a 64 bit system, if you have an array of 4 numbers, it will take 32 bytes of memory. 10 | 11 | # Arrays in dynamic languages 12 | 13 | Arrays in languages like JavaScript are dynamic. They tend to fool you into thinking they are more efficient than they are. 14 | 15 | For example, if you have an array of 4 numbers, it will take 32 bytes of memory. But if you add a 5th number, it will create a new array of 8 numbers and copy the first 4 numbers into it. Then, it will add the 5th number. So, it will take 64 bytes of memory. 16 | 17 | How dynamic arrays work: They create a new array with double the size of the old array. Then, they copy the old array into the new array. Then, they add the new item. 18 | 19 | # Arrays in static languages 20 | 21 | In languages like C, arrays are static. They are fixed in size. You have to specify the size of the array when you create it. 22 | 23 | # Big O Notation 24 | 25 | To understand the Big O of arrays, we need to understand how arrays work in memory. 26 | 27 | For example, initializing an array takes O(n) time because it has to go over each item and store each byte in memory. A byte is 8 bits. A bit is a 0 or a 1. 28 | 29 | The program has to go and find a row of memory that is empty and has enough space for the array. Then, it has to go over each item and store it in memory. 30 | 31 | So if the array has to get larger because you add an item, it has to copy the array into a new array, add the new item and then find a row of memory that is empty and has enough space for the new array. 32 | -------------------------------------------------------------------------------- /src/arrays/index.js: -------------------------------------------------------------------------------- 1 | // Simple, Dynamic Array in JS 2 | const arrWithNumbers = [1, 2, 3, 4, 5] 3 | 4 | arrWithNumbers.pop() // [1, 2, 3, 4] 5 | arrWithNumbers.push(6) // [1, 2, 3, 4, 6] 6 | arrWithNumbers.shift() // [2, 3, 4, 6] 7 | arrWithNumbers.unshift(1) // [1, 2, 3, 4, 6] 8 | 9 | // 2D Array in JS 10 | // First index is the row 11 | // Second index is the column 12 | const arrWithRowsAndColumns = [ 13 | [1, 2, 3], 14 | [4, 5, 6], 15 | [7, 8, 9], 16 | ] 17 | 18 | const firstRow = arrWithRowsAndColumns[0] // [1, 2, 3] 19 | const secondColumnInFirstRow = arrWithRowsAndColumns[0][1] // 2 20 | const thirdColumnInSecondRow = arrWithRowsAndColumns[1][2] // 6 21 | -------------------------------------------------------------------------------- /src/arrays/prefixSum.js: -------------------------------------------------------------------------------- 1 | class PrefixSum { 2 | constructor(nums) { 3 | this.prefix = [] 4 | this.total = 0 5 | 6 | for (let i = 0; i < nums.length; i++) { 7 | this.total += nums[i] 8 | this.prefix.push(this.total) 9 | } 10 | } 11 | 12 | rangeSum(left, right) { 13 | let preRight = this.prefix[right] 14 | let preLeft = left > 0 ? this.prefix[left - 1] : 0 15 | return preRight - preLeft 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/dynamic-programming/fibMemoization.js: -------------------------------------------------------------------------------- 1 | export const fibMemoization = (n, memo = {}) => { 2 | if (n <= 2) return n 3 | 4 | if (n in memo) return memo[n] 5 | 6 | memo[n] = fibMemoization(n - 1, memo) + fibMemoization(n - 2, memo) 7 | return memo[n] 8 | } 9 | -------------------------------------------------------------------------------- /src/dynamic-programming/learning-2-dimensional.js: -------------------------------------------------------------------------------- 1 | // Brute Force - Time: O(2 ^ (n + m)), Space: O(n + m) 2 | function bruteForce(r, c, rows, cols) { 3 | if (r == rows || c == cols) { 4 | return 0 5 | } 6 | 7 | if (r == rows - 1 && c == cols - 1) { 8 | return 1 9 | } 10 | 11 | const down = bruteForce(r + 1, c, rows, cols) 12 | const right = bruteForce(r, c + 1, rows, cols) 13 | 14 | return down + right 15 | } 16 | 17 | function withMemoization(r, c, rows, cols, memo) { 18 | if (r === rows || c === cols) { 19 | return 0 20 | } 21 | 22 | if (r === rows - 1 && c === cols - 1) { 23 | return 1 24 | } 25 | 26 | // Avoid recalculating the same subproblem 27 | // e.g. if we're on the second row, we don't want to have to recalculate it all the way to the destination again 28 | // This would mean in 4x4 grid, we'd have to recalculate entire third and fourth row 29 | if (memo[r][c] !== 0) { 30 | return memo[r][c] 31 | } 32 | 33 | const down = withMemoization(r + 1, c, rows, cols, memo) 34 | const right = withMemoization(r, c + 1, rows, cols, memo) 35 | 36 | // This is the same as the brute force solution, but we're storing the result in the memo 37 | // The result means "From this cell, how many ways are there to reach the destination?" 38 | // At the bottom row, there is only one way to reach the destination 39 | memo[r][c] = down + right 40 | 41 | return memo[r][c] 42 | } 43 | 44 | function withTabulation(rows, cols) { 45 | let prevRow = Array(cols).fill(0) 46 | 47 | for (let row = rows - 1; row >= 0; row--) { 48 | let currentRow = Array(cols).fill(0) 49 | currentRow[cols - 1] = 1 50 | 51 | for (let col = cols - 2; col >= 0; col--) { 52 | currentRow[col] = currentRow[col + 1] + prevRow[col] 53 | } 54 | 55 | prevRow = currentRow 56 | } 57 | 58 | return prevRow[0] 59 | } 60 | -------------------------------------------------------------------------------- /src/dynamic-programming/minCostClimbingStairs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} cost 3 | * @return {number} 4 | */ 5 | var minCostClimbingStairs = function (cost) { 6 | const len = cost.length 7 | const dp = new Array(len) 8 | 9 | dp[0] = cost[0] 10 | dp[1] = cost[1] 11 | 12 | for (let i = 2; i < len; i++) { 13 | const costOneStepAgo = dp[i - 1] + cost[i] 14 | const costTwoStepsAgo = dp[i - 2] + cost[i] 15 | const minimumCostUpToThisPoint = Math.min(costOneStepAgo, costTwoStepsAgo) 16 | dp[i] = minimumCostUpToThisPoint 17 | } 18 | 19 | return Math.min(dp[len - 1], dp[len - 2]) 20 | } 21 | -------------------------------------------------------------------------------- /src/dynamic-programming/minCostPath.js: -------------------------------------------------------------------------------- 1 | export const minCostPath = (grid, row = 0, column = 0, accumulator = 0) => { 2 | const isLastRow = row === grid.length - 1 3 | const isLastColumn = column === grid[0].length - 1 4 | 5 | if (isLastColumn && isLastRow) { 6 | const lastValue = grid[row][column] 7 | return accumulator + lastValue 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/dynamic-programming/minSubArraySum.js: -------------------------------------------------------------------------------- 1 | export const minSubArraySumDp = (array) => { 2 | if (array.length === 0) return 0 3 | 4 | let minSumUsingLastElementSum = array[0] 5 | let minSum = array[0] 6 | 7 | for (let i = 1; i < array.length; i++) { 8 | const minSumForCurrentElement = Math.min( 9 | array[i], 10 | array[i] + minSumUsingLastElementSum 11 | ) 12 | minSumUsingLastElementSum = minSumForCurrentElement 13 | minSum = Math.min(minSumForCurrentElement, minSum) 14 | } 15 | 16 | return minSum 17 | } 18 | -------------------------------------------------------------------------------- /src/dynamic-programming/minSubArraySum.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { minSubArraySumDp } from './minSubArraySum' 3 | 4 | it('minSubArraySumDp', () => { 5 | const array = [1, 2, 3, 4, 5, 6, 7, 8] 6 | expect(minSubArraySumDp(array)).toBe(1) 7 | }) 8 | -------------------------------------------------------------------------------- /src/dynamic-programming/tribonacci.js: -------------------------------------------------------------------------------- 1 | // /** 2 | // * @param {number} n 3 | // * @return {number} 4 | // */ 5 | // var tribonacci = function (n, cache = {}) { 6 | // if (n < 2) return n 7 | // if (n === 2) return 1 8 | 9 | // if (cache[n] !== undefined) { 10 | // return cache[n] 11 | // } 12 | 13 | // cache[n] = tribonacci(n - 1) + tribonacci(n - 2) + tribonacci(n - 3) 14 | 15 | // return cache[n] 16 | // } 17 | 18 | /** 19 | * @param {number} n 20 | * @return {number} 21 | */ 22 | var tribonacci = function (n) { 23 | if (n === 0) return 0 24 | if (n === 1 || n === 2) return 1 25 | 26 | let dp = [0, 1, 1] 27 | 28 | for (let i = 3; i <= n; i++) { 29 | let sum = dp[0] + dp[1] + dp[2] 30 | 31 | dp[0] = dp[1] 32 | dp[1] = dp[2] 33 | dp[2] = sum 34 | } 35 | 36 | return dp[2] 37 | } 38 | -------------------------------------------------------------------------------- /src/graphs/adj-list-directed.js: -------------------------------------------------------------------------------- 1 | export class AdjListDirected { 2 | constructor() { 3 | this.list = new Map() 4 | } 5 | 6 | addVertex(vertex) { 7 | const hasVertex = this.list.has(vertex) 8 | 9 | if (hasVertex) throw new Error('Vertex already exists') 10 | 11 | this.list.set(vertex, []) 12 | } 13 | 14 | hasVertex(vertex) { 15 | const hasVertex = this.list.has(vertex) 16 | return hasVertex 17 | } 18 | 19 | addEdge(vertex1, vertex2) { 20 | const hasVertex1 = this.list.has(vertex1) 21 | const hasVertex2 = this.list.has(vertex2) 22 | 23 | if (!hasVertex1 || !hasVertex2) { 24 | throw new Error('Vertex does not exist') 25 | } 26 | 27 | const edgesForVertex1 = this.list.get(vertex1) 28 | const newEdgesForVertex1 = [...edgesForVertex1, vertex2] 29 | this.list.set(vertex1, newEdgesForVertex1) 30 | } 31 | 32 | hasEdge(vertex1, vertex2) { 33 | const edgesForVertex1 = this.list.get(vertex1) 34 | return edgesForVertex1.includes(vertex2) 35 | } 36 | 37 | removeEdge(vertex1, vertex2) { 38 | const hasVertex1 = this.list.has(vertex1) 39 | const hasVertex2 = this.list.has(vertex2) 40 | 41 | if (!hasVertex1 || !hasVertex2) { 42 | throw new Error('Vertex does not exist') 43 | } 44 | 45 | const edgesForVertex1 = this.list.get(vertex1) 46 | const newEdgesForVertex1 = edgesForVertex1.filter( 47 | (vertex) => vertex !== vertex2 48 | ) 49 | 50 | this.list.set(vertex1, newEdgesForVertex1) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/graphs/adj-list-undirected.js: -------------------------------------------------------------------------------- 1 | export class AdjListUndirected { 2 | constructor() { 3 | this.list = new Map() 4 | } 5 | 6 | addVertex(vertex) { 7 | const isExistingVertex = this.list.has(vertex) 8 | 9 | if (isExistingVertex) throw new Error('Vertex already exists') 10 | 11 | this.list.set(vertex, []) 12 | } 13 | 14 | hasVertex(vertex) { 15 | const isExistingVertex = this.list.has(vertex) 16 | return isExistingVertex 17 | } 18 | 19 | addEdge(vertex1, vertex2) { 20 | const edgesForVertex1 = this.list.get(vertex1) 21 | const edgesForVertex2 = this.list.get(vertex2) 22 | 23 | const newEdgesForVertex1 = [...edgesForVertex1, vertex2] 24 | const newEdgesForVertex2 = [...edgesForVertex2, vertex1] 25 | 26 | this.list.set(vertex1, newEdgesForVertex1) 27 | this.list.set(vertex2, newEdgesForVertex2) 28 | } 29 | 30 | hasEdge(vertex1, vertex2) { 31 | const edgesForVertex1 = this.list.get(vertex1) 32 | const edgesForVertex2 = this.list.get(vertex2) 33 | 34 | const isVertex1InVertex2Edges = edgesForVertex2.includes(vertex1) 35 | const isVertex2InVertex1Edges = edgesForVertex1.includes(vertex2) 36 | 37 | return isVertex1InVertex2Edges && isVertex2InVertex1Edges 38 | } 39 | 40 | removeEdge(vertex1, vertex2) { 41 | const edgesForVertex1 = this.list.get(vertex1) 42 | const edgesForVertex2 = this.list.get(vertex2) 43 | 44 | const newEdgesForVertex1 = edgesForVertex1.filter( 45 | (edge) => edge !== vertex2 46 | ) 47 | const newEdgesForVertex2 = edgesForVertex2.filter( 48 | (edge) => edge !== vertex1 49 | ) 50 | 51 | this.list.set(vertex1, newEdgesForVertex1) 52 | this.list.set(vertex2, newEdgesForVertex2) 53 | } 54 | 55 | dfs(startingVertex, visited = new Set(), result = []) { 56 | if (!visited.has(startingVertex)) { 57 | visited.add(startingVertex) 58 | result.push(startingVertex) 59 | } 60 | 61 | const edges = this.list.get(startingVertex) 62 | edges.forEach((edge) => { 63 | if (!visited.has(edge)) { 64 | this.dfs(edge, visited, result) 65 | } 66 | }) 67 | 68 | return result 69 | } 70 | 71 | bfs(startingVertex) { 72 | const visited = new Set() 73 | const queue = [startingVertex] 74 | visited.add(startingVertex) 75 | const result = [startingVertex] 76 | 77 | while (queue.length) { 78 | const currentVertex = queue.shift() 79 | const edges = this.list.get(currentVertex) 80 | edges.forEach((edge) => { 81 | if (!visited.has(edge)) { 82 | result.push(edge) 83 | visited.add(edge) 84 | queue.push(edge) 85 | } 86 | }) 87 | } 88 | 89 | return result 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/graphs/adj-list-undirected.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { AdjListUndirected } from './adj-list-undirected' 3 | 4 | // Test for creating a new graph 5 | it('should create a new graph', () => { 6 | const graph = new AdjListUndirected() 7 | expect(graph).toBeDefined() 8 | }) 9 | 10 | // Test for adding a vertex 11 | it('should add vertices correctly', () => { 12 | const graph = new AdjListUndirected() 13 | graph.addVertex('A') 14 | graph.addVertex('B') 15 | expect(graph.hasVertex('A')).toBe(true) 16 | expect(graph.hasVertex('B')).toBe(true) 17 | expect(graph.hasVertex('C')).toBe(false) 18 | }) 19 | 20 | // Test for adding an edge 21 | it('should add an edge correctly', () => { 22 | const graph = new AdjListUndirected() 23 | graph.addVertex('A') 24 | graph.addVertex('B') 25 | graph.addEdge('A', 'B') 26 | expect(graph.hasEdge('A', 'B')).toBe(true) 27 | expect(graph.hasEdge('B', 'A')).toBe(true) // Undirected graph 28 | }) 29 | 30 | // Test for removing an edge 31 | it('should remove an edge correctly', () => { 32 | const graph = new AdjListUndirected() 33 | graph.addVertex('A') 34 | graph.addVertex('B') 35 | graph.addEdge('A', 'B') 36 | graph.removeEdge('A', 'B') 37 | expect(graph.hasEdge('A', 'B')).toBe(false) 38 | expect(graph.hasEdge('B', 'A')).toBe(false) // Undirected graph 39 | }) 40 | 41 | // Test for ensuring that non-existing edges are handled correctly 42 | it('should handle non-existing edges correctly', () => { 43 | const graph = new AdjListUndirected() 44 | graph.addVertex('A') 45 | graph.addVertex('B') 46 | expect(graph.hasEdge('A', 'B')).toBe(false) 47 | }) 48 | 49 | // Setup a sample graph 50 | function createSampleGraph() { 51 | const graph = new AdjListUndirected() 52 | // A graph that looks like: 53 | // A 54 | // / \ 55 | // B C 56 | // | | 57 | // D --- E 58 | // / \ 59 | // F G 60 | graph.addVertex('A') 61 | graph.addVertex('B') 62 | graph.addVertex('C') 63 | graph.addVertex('D') 64 | graph.addVertex('E') 65 | graph.addVertex('F') 66 | graph.addVertex('G') 67 | graph.addEdge('A', 'B') 68 | graph.addEdge('A', 'C') 69 | graph.addEdge('B', 'D') 70 | graph.addEdge('C', 'E') 71 | graph.addEdge('D', 'E') 72 | graph.addEdge('E', 'F') 73 | graph.addEdge('E', 'G') 74 | return graph 75 | } 76 | 77 | // Test for Depth-First Search (DFS) 78 | it('should correctly perform DFS traversal', () => { 79 | const graph = createSampleGraph() 80 | const dfsResult = graph.dfs('A') 81 | expect(dfsResult).toEqual(['A', 'B', 'D', 'E', 'C', 'F', 'G']) 82 | }) 83 | 84 | // Test for Breadth-First Search (BFS) 85 | it('should correctly perform BFS traversal', () => { 86 | const graph = createSampleGraph() 87 | const bfsResult = graph.bfs('A') 88 | expect(bfsResult).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G']) 89 | }) 90 | -------------------------------------------------------------------------------- /src/graphs/adj-list-weighted.js: -------------------------------------------------------------------------------- 1 | import { MinPriorityQueue } from '../queues/MinPriorityQueue' 2 | 3 | class Node { 4 | constructor(value, weight) { 5 | this.value = value 6 | this.weight = weight 7 | } 8 | } 9 | 10 | export class WeightedGraph { 11 | constructor() { 12 | this.list = new Map() 13 | } 14 | 15 | #checkBothVertices(vertex1, vertex2) { 16 | const hasVertex1 = this.list.has(vertex1) 17 | const hasVertex2 = this.list.has(vertex2) 18 | 19 | if (!hasVertex1 || !hasVertex2) throw new Error('Invalid vertices') 20 | } 21 | 22 | addVertex(vertex) { 23 | const hasVertex = this.list.has(vertex) 24 | 25 | if (hasVertex) throw new Error('Vertex already exists') 26 | 27 | this.list.set(vertex, []) 28 | } 29 | 30 | hasVertex(vertex) { 31 | const hasVertex = this.list.has(vertex) 32 | return hasVertex 33 | } 34 | 35 | addEdge(vertex1, vertex2, weight) { 36 | this.#checkBothVertices(vertex1, vertex2) 37 | 38 | const edgesForVertex1 = this.list.get(vertex1) 39 | const edgesForVertex2 = this.list.get(vertex2) 40 | 41 | const nodeForVertex1 = new Node(vertex2, weight) 42 | const nodeForVertex2 = new Node(vertex1, weight) 43 | 44 | const newEdgesForVertex1 = [...edgesForVertex1, nodeForVertex1] 45 | const newEdgesForVertex2 = [...edgesForVertex2, nodeForVertex2] 46 | 47 | this.list.set(vertex1, newEdgesForVertex1) 48 | this.list.set(vertex2, newEdgesForVertex2) 49 | } 50 | 51 | hasEdge(vertex1, vertex2) { 52 | this.#checkBothVertices(vertex1, vertex2) 53 | 54 | const edgesForVertex1 = this.list.get(vertex1) 55 | const edgesForVertex2 = this.list.get(vertex2) 56 | 57 | const isVertex1InVertex2Edges = edgesForVertex2.some( 58 | (edge) => edge.value === vertex1 59 | ) 60 | 61 | const isVertex2InVertex1Edges = edgesForVertex1.some( 62 | (edge) => edge.value === vertex2 63 | ) 64 | 65 | return isVertex1InVertex2Edges && isVertex2InVertex1Edges 66 | } 67 | 68 | getEdgeWeight(vertex1, vertex2) { 69 | this.#checkBothVertices(vertex1, vertex2) 70 | 71 | const edgesForVertex1 = this.list.get(vertex1) 72 | const edgesForVertex2 = this.list.get(vertex2) 73 | 74 | const edgeForVertex1 = edgesForVertex1.find( 75 | (edge) => edge.value === vertex2 76 | ) 77 | 78 | const edgeForVertex2 = edgesForVertex2.find( 79 | (edge) => edge.value === vertex1 80 | ) 81 | 82 | return edgeForVertex1.weight 83 | } 84 | 85 | removeEdge(vertex1, vertex2) { 86 | this.#checkBothVertices(vertex1, vertex2) 87 | 88 | const edgesForVertex1 = this.list.get(vertex1) 89 | const edgesForVertex2 = this.list.get(vertex2) 90 | 91 | const newEdgesForVertex1 = edgesForVertex1.filter( 92 | (edge) => edge.value !== vertex2 93 | ) 94 | 95 | const newEdgesForVertex2 = edgesForVertex2.filter( 96 | (edge) => edge.value !== vertex1 97 | ) 98 | 99 | this.list.set(vertex1, newEdgesForVertex1) 100 | this.list.set(vertex2, newEdgesForVertex2) 101 | } 102 | 103 | #findNodeWithSmallestDistance(unvisited, distances) { 104 | let smallestDistance = Infinity 105 | let smallestNode = null 106 | 107 | unvisited.forEach((node) => { 108 | if (distances[node] < smallestDistance) { 109 | smallestDistance = distances[node] 110 | smallestNode = node 111 | } 112 | }) 113 | 114 | return smallestNode 115 | } 116 | 117 | findShortestPathDijkstra(start, end) { 118 | const distances = new Map() 119 | const previousNodes = new Map() 120 | const pq = new MinPriorityQueue() 121 | const visited = new Set() 122 | 123 | // Initialize distances and priority queue 124 | this.list.forEach((_, vertex) => { 125 | distances.set(vertex, vertex === start ? 0 : Infinity) 126 | previousNodes.set(vertex, null) 127 | pq.enqueue(vertex, distances.get(vertex)) 128 | }) 129 | 130 | while (pq.size()) { 131 | const currentNode = pq.dequeue() 132 | visited.add(currentNode) 133 | 134 | if (currentNode === end) break 135 | 136 | const edgesForNode = this.list.get(currentNode) 137 | edgesForNode.forEach((edge) => { 138 | if (!visited.has(edge.value)) { 139 | const distanceToNeighbor = distances.get(currentNode) + edge.weight 140 | const currentNeighborDistance = distances.get(edge.value) 141 | 142 | if (distanceToNeighbor < currentNeighborDistance) { 143 | distances.set(edge.value, distanceToNeighbor) 144 | previousNodes.set(edge.value, currentNode) 145 | 146 | // Update priority in the priority queue 147 | pq.updatePriority(edge.value, distanceToNeighbor) 148 | } 149 | } 150 | }) 151 | } 152 | 153 | // Reconstruct the path from end to start 154 | let currentNode = end 155 | const path = [] 156 | 157 | // Check if a path exists 158 | const isNoPathFound = 159 | previousNodes.get(currentNode) === null && currentNode !== start 160 | if (isNoPathFound) { 161 | return [] 162 | } 163 | 164 | while (currentNode !== start) { 165 | path.unshift(currentNode) 166 | currentNode = previousNodes.get(currentNode) 167 | } 168 | 169 | path.unshift(start) 170 | 171 | return path 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/graphs/adj-list-weighted.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { WeightedGraph } from './adj-list-weighted' 3 | 4 | // Test for creating a new graph 5 | it('should create a new graph', () => { 6 | const graph = new WeightedGraph() 7 | expect(graph).toBeDefined() 8 | }) 9 | 10 | // Test for adding a vertex 11 | it('should add vertices correctly', () => { 12 | const graph = new WeightedGraph() 13 | graph.addVertex('A') 14 | graph.addVertex('B') 15 | expect(graph.hasVertex('A')).toBe(true) 16 | expect(graph.hasVertex('B')).toBe(true) 17 | }) 18 | 19 | // Test for adding a weighted edge 20 | it('should add weighted edges correctly', () => { 21 | const graph = new WeightedGraph() 22 | graph.addVertex('A') 23 | graph.addVertex('B') 24 | graph.addEdge('A', 'B', 5) 25 | expect(graph.hasEdge('A', 'B')).toBe(true) 26 | expect(graph.getEdgeWeight('A', 'B')).toBe(5) 27 | }) 28 | 29 | it('should remove an edge correctly', () => { 30 | const graph = new WeightedGraph() 31 | graph.addVertex('A') 32 | graph.addVertex('B') 33 | graph.addEdge('A', 'B', 5) 34 | graph.removeEdge('A', 'B') 35 | expect(graph.hasEdge('A', 'B')).toBe(false) 36 | }) 37 | 38 | it('should find the shortest path between two vertices', () => { 39 | const graph = new WeightedGraph() 40 | graph.addVertex('A') 41 | graph.addVertex('B') 42 | graph.addVertex('C') 43 | graph.addEdge('A', 'B', 1) 44 | graph.addEdge('B', 'C', 2) 45 | graph.addEdge('A', 'C', 4) 46 | 47 | const shortestPath = graph.findShortestPathDijkstra('A', 'C') 48 | expect(shortestPath).toEqual(['A', 'B', 'C']) // Expected path with total weight of 3 49 | }) 50 | -------------------------------------------------------------------------------- /src/graphs/adjacency-matrix.js: -------------------------------------------------------------------------------- 1 | export class AdjacencyMatrix { 2 | constructor(numberOfVertices) { 3 | this.numberOfVertices = numberOfVertices 4 | // 2d array 5 | this.matrix = Array.from({ length: numberOfVertices }, () => 6 | Array.from({ length: numberOfVertices }, () => 0) 7 | ) 8 | } 9 | 10 | addEdge(vertex1, vertex2) { 11 | this.matrix[vertex1][vertex2] = 1 12 | this.matrix[vertex2][vertex1] = 1 13 | } 14 | 15 | removeEdge(vertex1, vertex2) { 16 | this.matrix[vertex1][vertex2] = 0 17 | this.matrix[vertex2][vertex1] = 0 18 | } 19 | 20 | hasEdge(vertex1, vertex2) { 21 | return this.matrix[vertex1][vertex2] === 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/graphs/adjacency-matrix.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { AdjacencyMatrix } from './adjacency-matrix' 3 | 4 | it('should create a new graph with the correct number of vertices', () => { 5 | const graph = new AdjacencyMatrix(4) 6 | expect(graph.numberOfVertices).toBe(4) 7 | expect(graph.matrix.length).toBe(4) 8 | graph.matrix.forEach((row) => expect(row.length).toBe(4)) 9 | }) 10 | 11 | it('should correctly add an edge between two vertices', () => { 12 | const graph = new AdjacencyMatrix(4) 13 | graph.addEdge(0, 1) 14 | expect(graph.matrix[0][1]).toBe(1) 15 | expect(graph.matrix[1][0]).toBe(1) // If undirected graph 16 | }) 17 | 18 | it('should correctly remove an edge between two vertices', () => { 19 | const graph = new AdjacencyMatrix(4) 20 | graph.addEdge(0, 1) 21 | graph.removeEdge(0, 1) 22 | expect(graph.matrix[0][1]).toBe(0) 23 | expect(graph.matrix[1][0]).toBe(0) // If undirected graph 24 | }) 25 | 26 | it('should correctly identify if an edge exists', () => { 27 | const graph = new AdjacencyMatrix(4) 28 | graph.addEdge(0, 1) 29 | expect(graph.hasEdge(0, 1)).toBe(true) 30 | expect(graph.hasEdge(1, 0)).toBe(true) // If undirected graph 31 | expect(graph.hasEdge(0, 2)).toBe(false) 32 | }) 33 | 34 | it('should handle edges with invalid vertices', () => { 35 | const graph = new AdjacencyMatrix(4) 36 | expect(() => graph.addEdge(0, 4)).toThrow() 37 | expect(() => graph.addEdge(4, 0)).toThrow() 38 | }) 39 | -------------------------------------------------------------------------------- /src/graphs/ajd-list-directed.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { AdjListDirected } from './adj-list-directed' 3 | 4 | // Test for creating a new graph 5 | it('should create a new graph', () => { 6 | const graph = new AdjListDirected() 7 | expect(graph).toBeDefined() 8 | }) 9 | 10 | // Test for adding a vertex 11 | it('should add vertices correctly', () => { 12 | const graph = new AdjListDirected() 13 | graph.addVertex('A') 14 | graph.addVertex('B') 15 | expect(graph.hasVertex('A')).toBe(true) 16 | expect(graph.hasVertex('B')).toBe(true) 17 | expect(graph.hasVertex('C')).toBe(false) 18 | }) 19 | 20 | // Test for adding a directed edge 21 | it('should add a directed edge correctly', () => { 22 | const graph = new AdjListDirected() 23 | graph.addVertex('A') 24 | graph.addVertex('B') 25 | graph.addEdge('A', 'B') 26 | expect(graph.hasEdge('A', 'B')).toBe(true) 27 | expect(graph.hasEdge('B', 'A')).toBe(false) // Direction matters 28 | }) 29 | 30 | // Test for removing a directed edge 31 | it('should remove a directed edge correctly', () => { 32 | const graph = new AdjListDirected() 33 | graph.addVertex('A') 34 | graph.addVertex('B') 35 | graph.addEdge('A', 'B') 36 | graph.removeEdge('A', 'B') 37 | expect(graph.hasEdge('A', 'B')).toBe(false) 38 | }) 39 | 40 | // Test for ensuring that non-existing edges are handled correctly 41 | it('should handle non-existing edges correctly', () => { 42 | const graph = new AdjListDirected() 43 | graph.addVertex('A') 44 | graph.addVertex('B') 45 | expect(graph.hasEdge('A', 'B')).toBe(false) 46 | }) 47 | 48 | // Test for handling invalid vertices in edge operations 49 | it('should handle invalid vertices when adding or removing edges', () => { 50 | const graph = new AdjListDirected() 51 | graph.addVertex('A') 52 | expect(() => graph.addEdge('A', 'B')).toThrow() 53 | expect(() => graph.removeEdge('A', 'B')).toThrow() 54 | }) 55 | -------------------------------------------------------------------------------- /src/hash-tables/README.md: -------------------------------------------------------------------------------- 1 | # Hash Tables 2 | 3 | Hash tables are a data structure that maps keys to values for highly efficient lookup. There are a few different ways to implement a hash table, but the most common is to use an array of linked lists along with a **hash function** that converts a key into an array index. 4 | 5 | To visualize a hash table, imagine a bookshelf. When you want to find a book, you first need to know which shelf it's on. Then, you can go directly to that shelf, and look through the books on that shelf to find the book you want. 6 | 7 | The look up of the shelf is analagous to the hash function, and the search through the books is analagous to the linked list search. 8 | 9 | ``` 10 | [ 11 | Shelf 1: Book 1 -> Book 2 -> Book 3 12 | Shelf 2: Book 4 -> Book 5 -> Book 6 13 | Shelf 3: Book 7 -> Book 8 -> Book 9 14 | ] 15 | ``` 16 | 17 | Each book on the same shelf has the same hash value. 18 | 19 | To know the exact book you're looking for, you would look for the book's title for each on the shelf. This is analagous to the linked list search. 20 | 21 | # Hash Functions 22 | 23 | A hash function is a function that takes in a key and returns an index. The hash function should be deterministic. This means that the same key should always return the same index. 24 | 25 | For example, if we use ASCII, we can sum up the ASCII values of the characters in the key. Then we can use the modulo operator to get the remainder of the sum divided by the size of the array. This will give us an index. 26 | 27 | Let's say we have an array with size 3. 28 | 29 | Now we got the key "foo". For example purposes, let's say the ASCII values of the characters in the key "foo" is 300. 300 % 3 = 0. So we can store the value in the array at index 0. 30 | 31 | The same would be true for a key with same letters, but in a different order. For example, "ofo" would also have a sum of 300. 300 % 3 = 0. 32 | 33 | Had the value been 301, we would have stored the value in the array at index 1, because remainder of 301 % 3 = 1. 34 | 35 | # Resizing 36 | 37 | Resizing a hash table is important for maintaining its efficiency. Resizing occurs when the load factor (the ratio of the number of entries to the number of buckets) exceeds a certain threshold, commonly 0.7 or 75%. The primary goal is to reduce the number of collisions and maintain the time complexity of operations close to O(1). 38 | 39 | ## Steps in Resizing a Hash Table 40 | 41 | 1. **Allocate New, Larger Array:** 42 | 43 | - A new array, typically twice the size of the original, is allocated. This helps spread out the data more thinly, reducing collisions. 44 | 45 | 2. **Rehash All Entries:** 46 | 47 | - **Crucial Step:** Each existing entry must be rehashed according to the new array size. This is necessary because the hash values (array indices) are dependent on the array size. 48 | - **Process:** Iterate through the old array, compute the new index for each element using the hash function (adjusted for the new size), and insert it into the new array. 49 | 50 | 3. **Handle Collisions Anew:** 51 | 52 | - Even with a new array, collisions will occur and need to be handled using the chosen method (chaining or open addressing). 53 | 54 | 4. **Deallocate Old Array:** 55 | - Once all entries are moved to the new array, the memory allocated for the old array is deallocated (in languages where manual memory management is required). 56 | -------------------------------------------------------------------------------- /src/hash-tables/index.js: -------------------------------------------------------------------------------- 1 | import { SinglyLinkedList } from './linked-list' 2 | 3 | export class HashTable { 4 | constructor(size = 50) { 5 | // new arrays of null values 6 | this.data = new Array(size) 7 | } 8 | 9 | #hashKeyViaASCII(key) { 10 | let hash = 0 11 | 12 | for (let i = 0; i < key.length; i++) { 13 | hash += key.charCodeAt(i) 14 | } 15 | 16 | return hash 17 | } 18 | 19 | #turnHashToIndex(hash) { 20 | return hash % this.data.length 21 | } 22 | 23 | get(key) { 24 | const hashedKey = this.#hashKeyViaASCII(key) 25 | const index = this.#turnHashToIndex(hashedKey) 26 | const bucket = this.data[index] 27 | if (!bucket) return null 28 | 29 | const isOnlyOneItemInBucket = bucket.length === 1 30 | if (isOnlyOneItemInBucket && bucket.head.key === key) { 31 | return bucket.head.value 32 | } 33 | 34 | let currentNode = bucket.head 35 | 36 | while (currentNode) { 37 | if (currentNode.key === key) { 38 | return currentNode.value 39 | } 40 | 41 | currentNode = currentNode.next 42 | } 43 | 44 | return null 45 | } 46 | 47 | set(key, value) { 48 | const hashedKey = this.#hashKeyViaASCII(key) 49 | const index = this.#turnHashToIndex(hashedKey) 50 | const bucket = this.data[index] 51 | 52 | if (bucket) { 53 | bucket.append({ key, value }) 54 | } else { 55 | const newLinkedList = new SinglyLinkedList() 56 | newLinkedList.append({ key, value }) 57 | this.data[index] = newLinkedList 58 | } 59 | } 60 | 61 | remove(key) { 62 | const hashedKey = this.#hashKeyViaASCII(key) 63 | const index = this.#turnHashToIndex(hashedKey) 64 | const bucket = this.data[index] 65 | 66 | if (!bucket) return null 67 | 68 | let currentNode = bucket.head 69 | 70 | while (currentNode) { 71 | if (currentNode.key === key) { 72 | bucket.removeKey(key) 73 | } 74 | 75 | currentNode = currentNode.next 76 | } 77 | 78 | return null 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/hash-tables/index.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { HashTable } from './index' 3 | 4 | it('should store and retrieve values', () => { 5 | const hashTable = new HashTable(10) 6 | hashTable.set('key1', 'value1') 7 | hashTable.set('key2', 'value2') 8 | 9 | expect(hashTable.get('key1')).toBe('value1') 10 | expect(hashTable.get('key2')).toBe('value2') 11 | }) 12 | 13 | it('should handle collisions', () => { 14 | const hashTable = new HashTable(10) 15 | // These keys should result in the same hash index to test collision handling 16 | hashTable.set('key1', 'value1') 17 | hashTable.set('yk1e', 'value2') 18 | 19 | expect(hashTable.get('key1')).toBe('value1') 20 | expect(hashTable.get('yk1e')).toBe('value2') 21 | }) 22 | 23 | it('should return null for non-existent keys', () => { 24 | const hashTable = new HashTable(10) 25 | hashTable.set('key1', 'value1') 26 | 27 | expect(hashTable.get('key2')).toBeNull() 28 | }) 29 | 30 | it('should allow removing a value', () => { 31 | const hashTable = new HashTable(10) 32 | hashTable.set('key1', 'value1') 33 | hashTable.remove('key1') 34 | 35 | expect(hashTable.get('key1')).toBeNull() 36 | }) 37 | 38 | it('should handle removing values in case of collisions', () => { 39 | const hashTable = new HashTable(10) 40 | hashTable.set('key1', 'value1') 41 | hashTable.set('yk1e', 'value2') 42 | hashTable.remove('key1') 43 | 44 | expect(hashTable.get('key1')).toBeNull() 45 | expect(hashTable.get('yk1e')).toBe('value2') 46 | }) 47 | -------------------------------------------------------------------------------- /src/hash-tables/linked-list.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor({ value, key }) { 3 | this.value = value 4 | this.key = key 5 | this.next = null 6 | } 7 | } 8 | 9 | export class SinglyLinkedList { 10 | constructor() { 11 | this.head = null 12 | this.tail = null 13 | this.length = 0 14 | } 15 | 16 | append({ value, key }) { 17 | if (this.head === null) { 18 | this.head = new Node({ value, key }) 19 | this.tail = this.head 20 | } else { 21 | const newNode = new Node({ value, key }) 22 | this.tail.next = newNode 23 | this.tail = newNode 24 | } 25 | 26 | this.length++ 27 | } 28 | 29 | prepend({ value, key }) { 30 | if (this.head === null) { 31 | this.head = new Node({ value, key }) 32 | this.tail = this.head 33 | } else { 34 | const node = new Node({ value, key }) 35 | node.next = this.head 36 | this.head = node 37 | } 38 | 39 | this.length++ 40 | } 41 | 42 | clear() { 43 | this.head = null 44 | this.tail = null 45 | this.length = 0 46 | } 47 | 48 | toArray() { 49 | if (this.head === null) return [] 50 | 51 | const array = [] 52 | 53 | let currentNode = this.head 54 | 55 | while (currentNode !== null) { 56 | array.push(currentNode.value) 57 | currentNode = currentNode.next 58 | } 59 | 60 | return array 61 | } 62 | 63 | find(value) { 64 | if (this.head === null) return null 65 | 66 | let currentNode = this.head 67 | 68 | while (currentNode !== null) { 69 | if (currentNode.value === value) { 70 | return currentNode 71 | } 72 | 73 | currentNode = currentNode.next 74 | } 75 | 76 | return null 77 | } 78 | 79 | insert(index, values) { 80 | if (this.head === null) return null 81 | if (index === 0) { 82 | this.prepend(value) 83 | this.length++ 84 | return 85 | } 86 | 87 | const isIndexOutOfBounds = index < 0 || index > this.length 88 | 89 | if (isIndexOutOfBounds) return null 90 | 91 | let nodeBeforeIndexToBeInsertedAt = this.head 92 | 93 | for (let i = 0; i < index - 1; i++) { 94 | nodeBeforeIndexToBeInsertedAt = nodeBeforeIndexToBeInsertedAt.next 95 | } 96 | 97 | const newNode = new Node({ value: values.value, key: values.key }) 98 | newNode.next = nodeBeforeIndexToBeInsertedAt.next 99 | nodeBeforeIndexToBeInsertedAt.next = newNode 100 | this.length++ 101 | } 102 | 103 | removeAt(index) { 104 | if (this.head === null) return null 105 | 106 | const isIndexOutOfBounds = index < 0 || index > this.length 107 | if (isIndexOutOfBounds) return null 108 | 109 | if (index === 0) { 110 | return this.removeFirst() 111 | } 112 | 113 | let currentNode = this.head 114 | for (let i = 0; i < index - 1; i++) { 115 | currentNode = currentNode.next 116 | } 117 | 118 | const removed = currentNode.next 119 | currentNode.next = currentNode.next.next 120 | 121 | if (index === this.length - 1) { 122 | this.tail = currentNode 123 | } 124 | 125 | this.length-- 126 | return removed 127 | } 128 | 129 | removeKey(key) { 130 | if (this.head === null) return null 131 | 132 | let currentNode = this.head 133 | let nodeBeforeKey = null 134 | 135 | while (currentNode !== null) { 136 | if (currentNode.key === key) { 137 | if (nodeBeforeKey === null) { 138 | return this.removeFirst() 139 | } 140 | 141 | nodeBeforeKey.next = currentNode.next 142 | this.length-- 143 | return currentNode 144 | } 145 | 146 | nodeBeforeKey = currentNode 147 | currentNode = currentNode.next 148 | } 149 | 150 | return null 151 | } 152 | 153 | removeFirst() { 154 | if (this.head === null) return null 155 | 156 | const removed = this.head 157 | this.head = this.head.next 158 | this.length-- 159 | 160 | return removed 161 | } 162 | 163 | removeLast() { 164 | if (this.head === null) { 165 | return null 166 | } 167 | 168 | let tail = this.head 169 | let nodeBeforeTail = null 170 | 171 | // This loop stops at the last node as current.next will be null for the tail 172 | while (tail.next !== null) { 173 | nodeBeforeTail = tail 174 | tail = tail.next 175 | } 176 | 177 | nodeBeforeTail.next = null 178 | this.tail = nodeBeforeTail 179 | this.length-- 180 | 181 | return tail 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/heap/README.md: -------------------------------------------------------------------------------- 1 | # Min Heap 2 | 3 | Min Heap is a tree-based data structure that satisfies the heap property. The heap property states that for every node, the value of the node is less than or equal to the values of its children. The root node is the minimum element in the heap. 4 | 5 | How it looks like in array representation: 6 | 7 | ```text 8 | 1 9 | / \ 10 | 2 3 11 | / \ / \ 12 | 4 5 6 7 13 | ``` 14 | 15 | ``` 16 | [1, 2, 3, 4, 5, 6, 7] 17 | ``` 18 | 19 | As you can see, the root node is the minimum element in the heap. The array has a structure that represents a tree. 20 | 21 | ## Calculcations 22 | 23 | The parent of a node at index `i` is at index `Math.floor((i - 1) / 2)`. 24 | 25 | The left child of a node at index `i` is at index `2 * i + 1`. 26 | 27 | The right child of a node at index `i` is at index `2 * i + 2`. 28 | 29 | ## Bubble Up 30 | 31 | When we insert a new element into the heap, we add it to the end of the array. Then we compare the new element with its parent, if the new element is smaller than its parent, we swap them. We repeat this process until the new element is greater than its parent or it is the root node. 32 | 33 | ## Bubble Down 34 | 35 | When we remove the root node from the heap, we take the last element in the array and put it at the root. Then we compare the new root with its children, if the new root is greater than any of its children, we swap them. We repeat this process until the new root is less than both of its children or it is a leaf node. 36 | 37 | ## Heapify 38 | 39 | ```js 40 | heapify(array) { 41 | this.heap = array 42 | 43 | let indexOfLastNonLeafNode = this.#getIndexOfLastNonLeafNode() 44 | 45 | while (indexOfLastNonLeafNode >= 0) { 46 | this.#bubbleDown(indexOfLastNonLeafNode) 47 | indexOfLastNonLeafNode-- 48 | } 49 | } 50 | ``` 51 | 52 | `heapify` is interesting. It takes an array and turns it into a heap. It starts from the last non-leaf node and bubbles down each node until it reaches the root node. 53 | 54 | The last non-leaf node is the parent of the last element in the array. We can calculate the index of the last non-leaf node by using the formula `Math.floor((array.length - 2) / 2)`. 55 | 56 | This is a bit confusing, so in the code I've made it clearer with util functions. This is easier to understand because you quickly grasp we need the first minus 1 to get the index of last element: 57 | 58 | ```js 59 | #getIndexOfLastNonLeafNode() { 60 | const indexOfLastItem = this.heap.length - 1 61 | return this.#getParentIndex(indexOfLastItem) 62 | } 63 | ``` 64 | 65 | # Max Heap 66 | 67 | Similar to min heap, but the root node is the maximum element in the heap. So you can imagine how the other operations change because of it. 68 | -------------------------------------------------------------------------------- /src/heap/max-heap.js: -------------------------------------------------------------------------------- 1 | export class MaxHeap { 2 | constructor() { 3 | this.heap = [] 4 | } 5 | 6 | isEmpty() { 7 | return this.heap.length === 0 8 | } 9 | 10 | #swap(index1, index2) { 11 | let temp = this.heap[index1] 12 | this.heap[index1] = this.heap[index2] 13 | this.heap[index2] = temp 14 | } 15 | 16 | #getParentIndex(childIndex) { 17 | return Math.floor((childIndex - 1) / 2) 18 | } 19 | 20 | peek() { 21 | return this.heap[0] 22 | } 23 | 24 | #bubbleUp() { 25 | let indexOfInsertedNode = this.heap.length - 1 26 | let indexOfParentNode = this.#getParentIndex(indexOfInsertedNode) 27 | 28 | while (true) { 29 | const valueOfInsertedNode = this.heap[indexOfInsertedNode] 30 | const valueOfParentNode = this.heap[indexOfParentNode] 31 | 32 | if (valueOfParentNode < valueOfInsertedNode) { 33 | this.#swap(indexOfInsertedNode, indexOfParentNode) 34 | 35 | let tempIndex = indexOfParentNode 36 | indexOfInsertedNode = indexOfParentNode 37 | indexOfParentNode = this.#getParentIndex(tempIndex) 38 | 39 | continue 40 | } 41 | 42 | break 43 | } 44 | } 45 | 46 | #getLeftChildIndex(parentIndex) { 47 | return 2 * parentIndex + 1 48 | } 49 | 50 | #getRightChildIndex(parentIndex) { 51 | return 2 * parentIndex + 2 52 | } 53 | 54 | #bubbleDown(indexToBubbleDown = 0) { 55 | while (true) { 56 | const leftChildIndex = this.#getLeftChildIndex(indexToBubbleDown) 57 | const rightChildIndex = this.#getRightChildIndex(indexToBubbleDown) 58 | 59 | const currentValue = this.heap[indexToBubbleDown] 60 | const leftChildValue = this.heap[leftChildIndex] 61 | const rightChildValue = this.heap[rightChildIndex] 62 | 63 | const isCurrentLessThanBothChildren = 64 | currentValue < leftChildValue && currentValue < rightChildValue 65 | 66 | if (isCurrentLessThanBothChildren) { 67 | if (leftChildValue > rightChildValue) { 68 | this.#swap(leftChildIndex, indexToBubbleDown) 69 | indexToBubbleDown = leftChildIndex 70 | continue 71 | } 72 | 73 | if (rightChildValue > leftChildValue) { 74 | this.#swap(rightChildIndex, indexToBubbleDown) 75 | indexToBubbleDown = rightChildIndex 76 | continue 77 | } 78 | } 79 | 80 | if (leftChildValue > currentValue) { 81 | this.#swap(leftChildIndex, indexToBubbleDown) 82 | indexToBubbleDown = leftChildIndex 83 | continue 84 | } 85 | 86 | if (rightChildValue > currentValue) { 87 | this.#swap(rightChildIndex, indexToBubbleDown) 88 | indexToBubbleDown = rightChildIndex 89 | continue 90 | } 91 | 92 | break 93 | } 94 | } 95 | 96 | remove() { 97 | if (this.heap.length <= 1) return this.heap.pop() 98 | 99 | const removedValue = this.heap[0] 100 | this.heap[0] = this.heap.pop() 101 | this.#bubbleDown() 102 | 103 | return removedValue 104 | } 105 | 106 | insert(value) { 107 | this.heap.push(value) 108 | this.#bubbleUp() 109 | } 110 | 111 | #getIndexOfLastNonLeafNode() { 112 | const indexOfLastItem = this.heap.length - 1 113 | return this.#getParentIndex(indexOfLastItem) 114 | } 115 | 116 | heapify(array) { 117 | this.heap = array 118 | 119 | let indexOfLastNonLeafNode = this.#getIndexOfLastNonLeafNode() 120 | 121 | while (indexOfLastNonLeafNode >= 0) { 122 | this.#bubbleDown(indexOfLastNonLeafNode) 123 | indexOfLastNonLeafNode-- 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/heap/max-heap.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { MaxHeap } from './max-heap' 3 | 4 | // Test for creating a new max heap 5 | it('should create an empty max heap', () => { 6 | const maxHeap = new MaxHeap() 7 | expect(maxHeap).toBeDefined() 8 | expect(maxHeap.isEmpty()).toBe(true) 9 | }) 10 | 11 | // Test for inserting elements 12 | it('should insert elements and maintain max heap property', () => { 13 | const maxHeap = new MaxHeap() 14 | maxHeap.insert(3) 15 | maxHeap.insert(6) 16 | maxHeap.insert(1) 17 | 18 | expect(maxHeap.peek()).toBe(6) // Max element at the root 19 | }) 20 | 21 | // Test for removing the root element 22 | it('should remove the root element and maintain max heap property', () => { 23 | const maxHeap = new MaxHeap() 24 | maxHeap.insert(3) 25 | maxHeap.insert(6) 26 | maxHeap.insert(1) 27 | expect(maxHeap.remove()).toBe(6) // Remove max element 28 | expect(maxHeap.peek()).toBe(3) // Next max element at the root 29 | }) 30 | 31 | // Test for heapify 32 | it('should correctly heapify an array', () => { 33 | const maxHeap = new MaxHeap() 34 | maxHeap.heapify([3, 6, 1]) 35 | expect(maxHeap.peek()).toBe(6) // Max element at the root 36 | }) 37 | -------------------------------------------------------------------------------- /src/heap/min-heap.js: -------------------------------------------------------------------------------- 1 | export class MinHeap { 2 | constructor() { 3 | this.heap = [] 4 | } 5 | 6 | isEmpty() { 7 | return this.heap.length === 0 8 | } 9 | 10 | peek() { 11 | return this.heap[0] 12 | } 13 | 14 | #swap(index1, index2) { 15 | let temp = this.heap[index1] 16 | this.heap[index1] = this.heap[index2] 17 | this.heap[index2] = temp 18 | } 19 | 20 | #getParentIndex(childIndex) { 21 | return Math.floor((childIndex - 1) / 2) 22 | } 23 | 24 | #bubbleUp() { 25 | let indexOfInsertedNode = this.heap.length - 1 26 | let indexOfParentNode = this.#getParentIndex(indexOfInsertedNode) 27 | 28 | while (true) { 29 | const valueOfInsertedNode = this.heap[indexOfInsertedNode] 30 | const valueOfParentNode = this.heap[indexOfParentNode] 31 | 32 | if (valueOfParentNode > valueOfInsertedNode) { 33 | this.#swap(indexOfInsertedNode, indexOfParentNode) 34 | 35 | let tempIndex = indexOfParentNode 36 | indexOfInsertedNode = indexOfParentNode 37 | indexOfParentNode = this.#getParentIndex(tempIndex) 38 | 39 | continue 40 | } 41 | 42 | break 43 | } 44 | } 45 | 46 | insert(value) { 47 | this.heap.push(value) 48 | this.#bubbleUp() 49 | } 50 | 51 | #getLeftChildIndex(parentIndex) { 52 | return 2 * parentIndex + 1 53 | } 54 | 55 | #getRightChildIndex(parentIndex) { 56 | return 2 * parentIndex + 2 57 | } 58 | 59 | #bubbleDown(indexToBubbleDown = 0) { 60 | while (true) { 61 | const leftChildIndex = this.#getLeftChildIndex(indexToBubbleDown) 62 | const rightChildIndex = this.#getRightChildIndex(indexToBubbleDown) 63 | 64 | const currentValue = this.heap[indexToBubbleDown] 65 | const leftChildValue = this.heap[leftChildIndex] 66 | const rightChildValue = this.heap[rightChildIndex] 67 | 68 | // Current having less priority than both children, 69 | // in min heap this means that the current node is greater than both children 70 | const isCurrentLessPriorityThanBothChildren = 71 | leftChildValue < currentValue && rightChildValue < currentValue 72 | 73 | if (isCurrentLessPriorityThanBothChildren) { 74 | if (leftChildValue < rightChildValue) { 75 | this.#swap(leftChildIndex, indexToBubbleDown) 76 | indexToBubbleDown = leftChildIndex 77 | continue 78 | } 79 | 80 | if (rightChildValue < leftChildValue) { 81 | this.#swap(rightChildIndex, indexToBubbleDown) 82 | indexToBubbleDown = rightChildIndex 83 | continue 84 | } 85 | } 86 | 87 | if (leftChildValue < currentValue) { 88 | this.#swap(leftChildIndex, indexToBubbleDown) 89 | indexToBubbleDown = leftChildIndex 90 | continue 91 | } 92 | 93 | if (rightChildValue < currentValue) { 94 | this.#swap(rightChildIndex, indexToBubbleDown) 95 | indexToBubbleDown = rightChildIndex 96 | continue 97 | } 98 | 99 | break 100 | } 101 | } 102 | 103 | size() { 104 | return this.heap.length 105 | } 106 | 107 | remove() { 108 | if (this.heap.length === 1) { 109 | return this.heap.pop() 110 | } 111 | 112 | const lastItem = this.heap.pop() 113 | const firstItem = this.heap[0] 114 | 115 | this.heap[0] = lastItem 116 | this.#bubbleDown() 117 | 118 | return firstItem 119 | } 120 | 121 | #getIndexOfLastNonLeafNode() { 122 | const indexOfLastItem = this.heap.length - 1 123 | return this.#getParentIndex(indexOfLastItem) 124 | } 125 | 126 | heapify(array) { 127 | this.heap = array 128 | 129 | let indexOfLastNonLeafNode = this.#getIndexOfLastNonLeafNode() 130 | 131 | while (indexOfLastNonLeafNode >= 0) { 132 | this.#bubbleDown(indexOfLastNonLeafNode) 133 | indexOfLastNonLeafNode-- 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/heap/min-heap.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { MinHeap } from './min-heap' 3 | 4 | // Test for creating a new min heap 5 | it('should create an empty min heap', () => { 6 | const minHeap = new MinHeap() 7 | expect(minHeap).toBeDefined() 8 | expect(minHeap.isEmpty()).toBe(true) 9 | }) 10 | 11 | // Test for inserting elements 12 | it('should insert elements and maintain min heap property', () => { 13 | const minHeap = new MinHeap() 14 | minHeap.insert(3) 15 | minHeap.insert(1) 16 | minHeap.insert(6) 17 | 18 | expect(minHeap.peek()).toBe(1) 19 | }) 20 | 21 | // Test for removing the root element 22 | it('should remove the root element and maintain min heap property', () => { 23 | const minHeap = new MinHeap() 24 | minHeap.insert(3) 25 | minHeap.insert(1) 26 | minHeap.insert(6) 27 | expect(minHeap.remove()).toBe(1) 28 | expect(minHeap.peek()).toBe(3) 29 | }) 30 | 31 | it('should remove elements in priority order', () => { 32 | const minHeap = new MinHeap() 33 | minHeap.insert(3) 34 | minHeap.insert(1) 35 | minHeap.insert(2) 36 | 37 | expect(minHeap.remove()).toBe(1) 38 | expect(minHeap.remove()).toBe(2) 39 | expect(minHeap.remove()).toBe(3) 40 | expect(minHeap.isEmpty()).toBe(true) 41 | }) 42 | 43 | // Test for heapify 44 | it('should correctly heapify an array', () => { 45 | const minHeap = new MinHeap() 46 | minHeap.heapify([3, 1, 6]) 47 | expect(minHeap.peek()).toBe(1) 48 | }) 49 | 50 | it('should spit out size of heap', () => { 51 | const minHeap = new MinHeap() 52 | minHeap.insert(3) 53 | minHeap.insert(1) 54 | minHeap.insert(6) 55 | expect(minHeap.size()).toBe(3) 56 | }) 57 | -------------------------------------------------------------------------------- /src/interviews/isRealEmail.js: -------------------------------------------------------------------------------- 1 | // A function that checks if an email is real 2 | // This is done via a penalty system 3 | export function isRealEmail(email) { 4 | let score = 0 5 | const botKeywords = new Set(['no-reply', 'no-varg']) 6 | const suspiciousDomains = new Set(['tempmail.com', 'spammail.com']) 7 | const suspiciousTLDs = new Set(['.xyz', '.info']) 8 | 9 | // Split the email into local part and domain 10 | const [localPart, domain] = email.split('@') 11 | 12 | // Check for bot keywords 13 | botKeywords.forEach((keyword) => { 14 | if (email.includes(keyword)) score += 30 15 | }) 16 | 17 | // Check length of the local part 18 | if (localPart.length > 20) score += 20 19 | 20 | // Check for special characters 21 | if (email.includes('=')) score += 20 22 | 23 | // Check for randomness - simplistic check 24 | const randomPattern = /[0-9]{3,}/ // Simplistic check for sequences of numbers 25 | if (randomPattern.test(localPart)) score += 15 26 | 27 | // Domain reputation 28 | if (suspiciousDomains.has(domain)) score += 30 29 | 30 | // Check TLD 31 | const tld = domain.substring(domain.lastIndexOf('.')) 32 | if (suspiciousTLDs.has(tld)) score += 25 33 | 34 | // Adjust score to fit into 0-100 range if it exceeds 35 | score = Math.min(score, 100) 36 | 37 | // Consider email real if score is less than 50 38 | return score < 50 39 | } 40 | -------------------------------------------------------------------------------- /src/interviews/pickAvatarBackgroundColor.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | 3 | export function pickAvatarBackgroundColor(name, colors) { 4 | // Use SHA-256 hash function 5 | const hash = crypto.createHash('sha256') 6 | hash.update(name) 7 | // Convert the hash to a big integer (since the hash is a hex string) 8 | const hashInt = BigInt('0x' + hash.digest('hex')) 9 | // Use modulo to pick a color from the list 10 | const index = hashInt % BigInt(colors.length) 11 | return colors[Number(index)] 12 | } 13 | -------------------------------------------------------------------------------- /src/leetcode/arrays/arrayStringsAreEqual.js: -------------------------------------------------------------------------------- 1 | // /** 2 | // * @param {string[]} word1 3 | // * @param {string[]} word2 4 | // * @return {boolean} 5 | // */ 6 | // var arrayStringsAreEqual = function (word1, word2) { 7 | // let word1Str = '' 8 | // let word2Str = '' 9 | 10 | // // Concatenate strings in word1 11 | // for (let i = 0; i < word1.length; i++) { 12 | // word1Str += word1[i] 13 | // } 14 | 15 | // // Concatenate strings in word2 16 | // for (let j = 0; j < word2.length; j++) { 17 | // word2Str += word2[j] 18 | // } 19 | 20 | // // Compare the concatenated strings 21 | // return word1Str === word2Str 22 | // } 23 | 24 | /** 25 | * @param {string[]} word1 26 | * @param {string[]} word2 27 | * @return {boolean} 28 | */ 29 | var arrayStringsAreEqual = function (word1, word2) { 30 | let indexWord1 = 0, 31 | indexWord2 = 0 // Indexes for the arrays 32 | let charIndexWord1 = 0, 33 | charIndexWord2 = 0 // Indexes for the characters within the arrays' strings 34 | 35 | while (indexWord1 < word1.length && indexWord2 < word2.length) { 36 | if ( 37 | word1[indexWord1][charIndexWord1] !== word2[indexWord2][charIndexWord2] 38 | ) { 39 | return false // Characters do not match 40 | } 41 | 42 | charIndexWord1++ 43 | charIndexWord2++ 44 | 45 | // Move to the next character in word1 46 | if (charIndexWord1 === word1[indexWord1].length) { 47 | indexWord1++ 48 | charIndexWord1 = 0 49 | } 50 | 51 | // Move to the next character in word2 52 | if (charIndexWord2 === word2[indexWord2].length) { 53 | indexWord2++ 54 | charIndexWord2 = 0 55 | } 56 | } 57 | 58 | // Ensure both arrays were fully traversed and we are at the start of a new string in both 59 | return ( 60 | indexWord1 === word1.length && 61 | indexWord2 === word2.length && 62 | charIndexWord1 === 0 && 63 | charIndexWord2 === 0 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /src/leetcode/arrays/binarySearch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} target 4 | * @return {number} 5 | */ 6 | var search = function (nums, target) { 7 | let left = 0 8 | let right = nums.length - 1 9 | 10 | while (left <= right) { 11 | const mid = Math.floor((left + right) / 2) 12 | 13 | if (nums[mid] > target) { 14 | right = mid - 1 15 | } else if (nums[mid] < target) { 16 | left = mid + 1 17 | } else { 18 | return mid 19 | } 20 | } 21 | 22 | return -1 23 | } 24 | -------------------------------------------------------------------------------- /src/leetcode/arrays/characterReplacement.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {number} k 4 | * @return {number} 5 | */ 6 | var characterReplacement = function (s, k) { 7 | let count = {} 8 | let L = 0 9 | 10 | let longestRepeatingLength = 0 11 | let maxf = 0 12 | for (let R = 0; R < s.length; R++) { 13 | count[s[R]] = 1 + (count[s[R]] || 0) 14 | maxf = Math.max(count[s[R]], maxf) 15 | 16 | while (R - L + 1 - maxf > k) { 17 | count[s[L]] -= 1 18 | L++ 19 | } 20 | 21 | longestRepeatingLength = Math.max(longestRepeatingLength, R - L + 1) 22 | } 23 | 24 | return longestRepeatingLength 25 | } 26 | -------------------------------------------------------------------------------- /src/leetcode/arrays/concatenationOfArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[]} 4 | */ 5 | var getConcatenation = function (nums) { 6 | const ans = [] 7 | 8 | for (let i = 0; i < 2; i++) { 9 | for (let j = 0; j < nums.length; j++) { 10 | ans.push(nums[j]) 11 | } 12 | } 13 | 14 | return ans 15 | } 16 | -------------------------------------------------------------------------------- /src/leetcode/arrays/containsNearbyDuplicate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} k 4 | * @return {boolean} 5 | */ 6 | var containsNearbyDuplicate = function (nums, k) { 7 | const window = new Set() 8 | let L = 0 9 | 10 | const realK = k + 1 11 | 12 | for (let R = 0; R < nums.length; R++) { 13 | const isNewWindowExceedingKLength = R - L + 1 > realK 14 | 15 | if (isNewWindowExceedingKLength) { 16 | window.delete(nums[L]) 17 | L++ 18 | } 19 | 20 | if (window.has(nums[R])) { 21 | return true 22 | } 23 | 24 | window.add(nums[R]) 25 | } 26 | 27 | return false 28 | } 29 | -------------------------------------------------------------------------------- /src/leetcode/arrays/findDifference.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums1 3 | * @param {number[]} nums2 4 | * @return {number[][]} 5 | */ 6 | var findDifference = function (nums1, nums2) { 7 | const nums1Set = new Set(nums1) 8 | const nums2Set = new Set(nums2) 9 | 10 | let res1Set = new Set() 11 | let res2Set = new Set() 12 | 13 | for (let num of nums1) { 14 | if (!nums2Set.has(num)) { 15 | res1Set.add(num) 16 | } 17 | } 18 | 19 | for (let num of nums2) { 20 | if (!nums1Set.has(num)) { 21 | res2Set.add(num) 22 | } 23 | } 24 | 25 | // Convert sets back to arrays before returning 26 | return [Array.from(res1Set), Array.from(res2Set)] 27 | } 28 | -------------------------------------------------------------------------------- /src/leetcode/arrays/findDuplicate.js: -------------------------------------------------------------------------------- 1 | // /** 2 | // * @param {number[]} nums 3 | // * @return {number} 4 | // */ 5 | // var findDuplicate = function (nums) { 6 | // const set = new Set() 7 | 8 | // for (let i = 0; i < nums.length; i++) { 9 | // if (set.has(nums[i])) { 10 | // return nums[i] 11 | // } 12 | 13 | // set.add(nums[i]) 14 | // } 15 | // } 16 | 17 | function findDuplicate(nums) { 18 | let slow = 0 19 | let fast = 0 20 | 21 | do { 22 | slow = nums[slow] 23 | fast = nums[nums[fast]] 24 | } while (slow !== fast) 25 | 26 | let slow2 = 0 27 | 28 | while (slow !== slow2) { 29 | slow = nums[slow] 30 | slow2 = nums[slow2] 31 | } 32 | 33 | return slow 34 | } 35 | -------------------------------------------------------------------------------- /src/leetcode/arrays/insertionSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[]} 4 | */ 5 | var sortArray = function (nums) { 6 | for (let i = 1; i < nums.length; i++) { 7 | let j = i - 1 8 | 9 | while (j >= 0 && nums[j] > nums[j + 1]) { 10 | let tmp = nums[j + 1] 11 | nums[j + 1] = nums[j] 12 | 13 | nums[j] = tmp 14 | j-- 15 | } 16 | } 17 | 18 | return nums 19 | } 20 | -------------------------------------------------------------------------------- /src/leetcode/arrays/isBadVersion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for isBadVersion() 3 | * 4 | * @param {integer} version number 5 | * @return {boolean} whether the version is bad 6 | * isBadVersion = function(version) { 7 | * ... 8 | * }; 9 | */ 10 | 11 | /** 12 | * @param {function} isBadVersion() 13 | * @return {function} 14 | */ 15 | var solution = function (isBadVersion) { 16 | /** 17 | * @param {integer} n Total versions 18 | * @return {integer} The first bad version 19 | */ 20 | return function (n) { 21 | let left = 0 22 | let right = n 23 | 24 | while (left <= right) { 25 | const mid = Math.floor((left + right) / 2) 26 | 27 | if (isBadVersion(mid)) { 28 | right = mid 29 | } else { 30 | left = mid + 1 31 | } 32 | } 33 | 34 | if (left === right && isBadVersion(right)) { 35 | return right 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/leetcode/arrays/isMonotonic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {boolean} 4 | */ 5 | var isMonotonic = function (nums) { 6 | if (nums[nums.length - 1] - nums[0] < 0) { 7 | nums.reverse() 8 | } 9 | 10 | for (let i = 0; i < nums.length - 1; i++) { 11 | if (!(nums[i] <= nums[i + 1])) { 12 | return false 13 | } 14 | } 15 | 16 | return true 17 | } 18 | -------------------------------------------------------------------------------- /src/leetcode/arrays/isPalindrome.js: -------------------------------------------------------------------------------- 1 | var isPalindrome = function (s) { 2 | let L = 0 3 | let R = s.length - 1 4 | 5 | while (L < R) { 6 | while (L < R && !isLetterOrNumber(s[R])) { 7 | R-- 8 | } 9 | 10 | while (L < R && !isLetterOrNumber(s[L])) { 11 | L++ 12 | } 13 | 14 | if (s[L].toLowerCase() !== s[R].toLowerCase()) { 15 | return false 16 | } 17 | 18 | L++ 19 | R-- 20 | } 21 | 22 | return true 23 | } 24 | 25 | function isLetterOrNumber(char) { 26 | return /^[A-Za-z0-9]$/.test(char) 27 | } 28 | -------------------------------------------------------------------------------- /src/leetcode/arrays/kthLargestElement.js: -------------------------------------------------------------------------------- 1 | var findKthLargest = function (nums, k) { 2 | return quickSelect(nums, k) 3 | } 4 | 5 | function quickSelect(nums, k, start = 0, end = nums.length - 1) { 6 | let pivotIndex = partition(nums, start, end) 7 | 8 | let indexOfKthLargest = nums.length - k 9 | 10 | if (pivotIndex > indexOfKthLargest) { 11 | return quickSelect(nums, k, start, pivotIndex - 1) 12 | } else if (pivotIndex < indexOfKthLargest) { 13 | return quickSelect(nums, k, pivotIndex + 1, end) 14 | } else { 15 | return nums[pivotIndex] 16 | } 17 | } 18 | 19 | function partition(nums, start, end) { 20 | let pivotValue = nums[end] 21 | let pivotIndex = start 22 | 23 | for (let i = start; i < end; i++) { 24 | if (nums[i] <= pivotValue) { 25 | ;[nums[i], nums[pivotIndex]] = [nums[pivotIndex], nums[i]] 26 | pivotIndex++ 27 | } 28 | } 29 | 30 | ;[nums[pivotIndex], nums[end]] = [nums[end], nums[pivotIndex]] 31 | 32 | return pivotIndex 33 | } 34 | -------------------------------------------------------------------------------- /src/leetcode/arrays/lengthOfLongestSubstring.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var lengthOfLongestSubstring = function (s) { 6 | let longestLength = 0 7 | let L = 0 8 | 9 | let window = new Set() 10 | 11 | for (let R = 0; R < s.length; R++) { 12 | while (window.has(s[R])) { 13 | window.delete(s[L]) 14 | L++ 15 | } 16 | 17 | longestLength = Math.max(longestLength, R - L + 1) 18 | window.add(s[R]) 19 | } 20 | 21 | return longestLength 22 | } 23 | 24 | // https://leetcode.com/explore/learn/card/dynamic-programming/631/strategy-for-solving-dp-problems/4099/ 25 | -------------------------------------------------------------------------------- /src/leetcode/arrays/maxArea.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} height 3 | * @return {number} 4 | */ 5 | var maxArea = function (height) { 6 | let L = 0 7 | let R = height.length - 1 8 | 9 | let maxHeight = 0 10 | 11 | while (L < R) { 12 | const newHeight = (R - L) * Math.min(height[L], height[R]) 13 | maxHeight = Math.max(newHeight, maxHeight) 14 | 15 | if (height[L] > height[R]) { 16 | R-- 17 | } else { 18 | L++ 19 | } 20 | } 21 | 22 | return maxHeight 23 | } 24 | -------------------------------------------------------------------------------- /src/leetcode/arrays/maxSubArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var maxSubArray = function (nums) { 6 | // Globally keep track of the maximum sum 7 | let maxGlobal = nums[0] 8 | 9 | // Keep track of the maximum sum for the current subarray in the iteration 10 | let maxCurrent = nums[0] 11 | 12 | for (let i = 1; i < nums.length; i++) { 13 | // This is the key to the Kadane's algorithm 14 | // If the current number is greater than the sum of the current subarray 15 | // This means that we should start a new subarray 16 | // Otherwise, we should continue adding to the current subarray 17 | // So when we land on a number that is greater than the sum of the current subarray 18 | // We start a new subarray 19 | 20 | // If `Math.max(nums[i], nums[i] + maxCurrent)` is equal to `nums[i]`, then start a new subarray 21 | // Because the current number is greater than the sum of the current subarray 22 | 23 | // If `Math.max(nums[i], nums[i] + maxCurrent)` is equal to `nums[i] + maxCurrent`, then continue adding to the current subarray 24 | // Because the current number is less than the sum of the current subarray 25 | maxCurrent = Math.max(nums[i], nums[i] + maxCurrent) 26 | 27 | // Keep track of the maximum sum 28 | // Global should always be the maximum sum 29 | // If e.g. a new subarray was started because the previous sub array continued and encountered negative numbers 30 | // and eventually encountered a positive number, therefore started a new sub array 31 | // BUT if the previous sub array at some point was greater than the new sub array 32 | // Then Global should be the previous sub array at the time it was at it's peak 33 | maxGlobal = Math.max(maxGlobal, maxCurrent) 34 | } 35 | 36 | return maxGlobal 37 | } 38 | -------------------------------------------------------------------------------- /src/leetcode/arrays/maxSubarraySumCircular.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var maxSubarraySumCircular = function (nums) { 6 | let maxGlobal = nums[0] 7 | let currentSum = nums[0] 8 | 9 | let minGlobal = nums[0] 10 | let currentMin = nums[0] 11 | 12 | let total = nums[0] 13 | 14 | for (let i = 1; i < nums.length; i++) { 15 | // keep track of the maximum subarray sum 16 | currentSum = Math.max(nums[i], nums[i] + currentSum) 17 | maxGlobal = Math.max(currentSum, maxGlobal) 18 | 19 | // keep track of the minimum subarray sum 20 | currentMin = Math.min(nums[i], nums[i] + currentMin) 21 | minGlobal = Math.min(currentMin, minGlobal) 22 | 23 | // keep track of the total sum of the array 24 | total += nums[i] 25 | } 26 | 27 | // if total is equal to minGlobal, then return maxGlobal 28 | // this means all numbers are negative 29 | // this is because if all negative numbers add up to the total, they will be the least possible sum 30 | if (total === minGlobal) { 31 | return maxGlobal 32 | } 33 | 34 | // Otherwise, return the maximum of the total minus the minimum subarray sum 35 | // however, it may not be the answer 36 | // so we have to compare it with maxGlobal 37 | // these both are to right possible options, we want the maximum of the two 38 | return Math.max(maxGlobal, total - minGlobal) 39 | } 40 | -------------------------------------------------------------------------------- /src/leetcode/arrays/maxTurbulenceSize.js: -------------------------------------------------------------------------------- 1 | var maxTurbulenceSize = function (arr) { 2 | if (arr.length === 1) { 3 | return 1 4 | } 5 | 6 | let R = 1 7 | let L = 0 8 | 9 | let maxLength = 1 10 | 11 | let prev = '' 12 | 13 | while (R < arr.length) { 14 | if (arr[R - 1] > arr[R] && prev !== '>') { 15 | maxLength = Math.max(R - L + 1, maxLength) 16 | R++ 17 | prev = '>' 18 | } else if (arr[R - 1] < arr[R] && prev !== '<') { 19 | // R - L + 1 is the length of the subarray 20 | // Example: [9, 4, 2, 10, 7, 8, 8, 1, 9] 21 | // L = 1, and R = 6 22 | // R - L + 1 = 6 - 1 + 1 = 7 23 | // The length of the subarray is 7 24 | // These would be the numbers in the subarray: [4, 2, 10, 7, 8, 8, 1] 25 | maxLength = Math.max(maxLength, R - L + 1) 26 | R++ 27 | prev = '<' 28 | } else { 29 | // If the previous element is equal to the current element 30 | // We want Left pointer to be at the current element 31 | // and Right pointer to be at the next element 32 | // this is because "=" is not a valid operator to form a turbulent subarray 33 | 34 | // if that is not the case, we simply want to start a new subarray from the current element 35 | // Operator is either > or <, but same as last one, hence new subarray gotta be started 36 | R = arr[R] === arr[R - 1] ? R + 1 : R 37 | L = R - 1 38 | prev = '' 39 | } 40 | } 41 | 42 | return maxLength 43 | } 44 | -------------------------------------------------------------------------------- /src/leetcode/arrays/mergeSort.js: -------------------------------------------------------------------------------- 1 | function mergeSort(arr) { 2 | if (arr.length <= 1) { 3 | return arr 4 | } 5 | 6 | let middle = Math.floor(arr.length / 2) 7 | 8 | let left = mergeSort(arr.slice(0, middle)) 9 | let right = mergeSort(arr.slice(middle)) 10 | 11 | return merge(left, right) 12 | } 13 | 14 | function merge(left, right) { 15 | let result = [] 16 | 17 | let leftIndex = 0 18 | let rightIndex = 0 19 | 20 | while (leftIndex < left.length && rightIndex < right.length) { 21 | if (left[leftIndex] < right[rightIndex]) { 22 | result.push(left[leftIndex]) 23 | left++ 24 | } else { 25 | result.push(right[rightIndex]) 26 | rightIndex++ 27 | } 28 | } 29 | 30 | while (leftIndex < left.length) { 31 | result.push(left[leftIndex]) 32 | leftIndex++ 33 | } 34 | 35 | while (rightIndex < right.length) { 36 | result.push(right[rightIndex]) 37 | rightIndex++ 38 | } 39 | 40 | return result 41 | } 42 | -------------------------------------------------------------------------------- /src/leetcode/arrays/minEatingSpeed.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tigerabrodi/data-structures-and-algorithms-javascript/ab0f87c3cf68fe8cbbeaa3a4887aaf24363ea2f7/src/leetcode/arrays/minEatingSpeed.js -------------------------------------------------------------------------------- /src/leetcode/arrays/minSubArrayLen.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} target 3 | * @param {number[]} nums 4 | * @return {number} 5 | */ 6 | var minSubArrayLen = function (target, nums) { 7 | let minimumLength = Infinity 8 | 9 | let curSum = 0 10 | let L = 0 11 | 12 | for (let R = 0; R < nums.length; R++) { 13 | curSum += nums[R] 14 | 15 | while (curSum >= target) { 16 | let currentLength = R - L + 1 17 | minimumLength = Math.min(minimumLength, currentLength) 18 | curSum -= nums[L] 19 | L++ 20 | } 21 | } 22 | 23 | if (minimumLength === Infinity) { 24 | return 0 25 | } 26 | 27 | return minimumLength 28 | } 29 | -------------------------------------------------------------------------------- /src/leetcode/arrays/numOfSubarrays.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} arr 3 | * @param {number} k 4 | * @param {number} threshold 5 | * @return {number} 6 | */ 7 | var numOfSubarrays = function (arr, k, threshold) { 8 | let L = 0 9 | let numberOfSubArrays = 0 10 | 11 | let currentSum = 0 12 | 13 | for (let R = 0; R < arr.length; R++) { 14 | currentSum += arr[R] 15 | 16 | const isWindowEqualToLength = R - L + 1 === k 17 | 18 | if (isWindowEqualToLength) { 19 | let average = currentSum / k 20 | if (average >= threshold) { 21 | numberOfSubArrays++ 22 | } 23 | 24 | currentSum -= arr[L] 25 | L++ 26 | } 27 | } 28 | 29 | return numberOfSubArrays 30 | } 31 | -------------------------------------------------------------------------------- /src/leetcode/arrays/pivotIndex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var pivotIndex = function (nums) { 6 | let total = 0 7 | 8 | for (let i = 0; i < nums.length; i++) { 9 | total += nums[i] 10 | } 11 | 12 | let leftSum = 0 13 | 14 | for (let j = 0; j < nums.length; j++) { 15 | let rightSum = total - leftSum - nums[j] 16 | 17 | if (leftSum === rightSum) { 18 | return j 19 | } 20 | 21 | leftSum += nums[j] 22 | } 23 | 24 | return -1 25 | } 26 | -------------------------------------------------------------------------------- /src/leetcode/arrays/productExceptSelf.js: -------------------------------------------------------------------------------- 1 | function productExceptSelf(nums) { 2 | // 1. Get the prefix sum 3 | let prefix = [] 4 | let totalForPrefix = 1 5 | 6 | for (let i = 0; i < nums.length; i++) { 7 | totalForPrefix *= nums[i] 8 | prefix.push(totalForPrefix) 9 | } 10 | 11 | // 2. Get the postfix sum 12 | let postfix = [] 13 | let totalForPostfix = 1 14 | 15 | for (let i = nums.length - 1; i >= 0; i--) { 16 | totalForPostfix *= nums[i] 17 | 18 | // Here we use `unshift` to add the value to the beginning of the array 19 | 20 | // if we had [1, 2, 3, 4] 21 | // It'd look like this: 22 | // First iteration [] -> 1 * 4 = 4 -> [4] 23 | // Second iteration [4] -> 3 * 4 = 12 -> [12, 4] 24 | // Third iteration [12, 4] -> 2 * 12 = 24 -> [24, 12, 4] 25 | // Fourth iteration [24, 12, 4] -> 1 * 24 = 24 -> [24, 24, 12, 4] 26 | 27 | // We effectively sum up the product of the numbers from the end of the array to the beginning 28 | // This way when calculating the result, we can use prefix[i - 1] * postfix[i + 1] 29 | postfix.unshift(totalForPostfix) 30 | } 31 | 32 | // prefix would be [1, 2, 6, 24] 33 | // postfix would be [24, 24, 12, 4] 34 | 35 | // if we had [1, 2, 3, 4] 36 | // let's say we want the value at position 1, which is 2 37 | // We would multiply the prefix value at position 0, which is 1 38 | // With the postfix value at position 2, which is 12 39 | // So the result would be 1 * 12 = 12 40 | 41 | let result = [] 42 | 43 | for (let i = 0; i < nums.length; i++) { 44 | let pre = i > 0 ? prefix[i - 1] : 1 45 | let post = i < nums.length - 1 ? postfix[i + 1] : 1 46 | result.push(pre * post) 47 | } 48 | 49 | return result 50 | } 51 | -------------------------------------------------------------------------------- /src/leetcode/arrays/quickSort.js: -------------------------------------------------------------------------------- 1 | function quickSort(arr, start = 0, end = arr.length - 1) { 2 | // Base condition to end recursion 3 | // If start is greater than or equal to end, the array is already sorted 4 | // and does not need to be partitioned further 5 | // For example, an array of 1 element is already sorted 6 | // and an array of 0 elements is already sorted 7 | if (start >= end) return arr 8 | 9 | // Partition the array and get the pivot's final position 10 | let pivotIndex = partition(arr, start, end) 11 | 12 | // Recursively sort elements before the pivot 13 | quickSort(arr, start, pivotIndex - 1) 14 | 15 | // Recursively sort elements after the pivot 16 | quickSort(arr, pivotIndex + 1, end) 17 | 18 | return arr 19 | } 20 | 21 | function partition(arr, start, end) { 22 | // Choosing the pivot as the last element of the segment 23 | const pivotValue = arr[end] 24 | let pivotIndex = start // This will track the final index of the pivot 25 | 26 | // for example: [10, 6, 2, 5, 7, 3, 4] 27 | // pivotValue = 4 28 | // pivotIndex = 0 29 | // start = 0 30 | // end = 6 31 | 32 | // 10 is greater than 4, so it needs to be moved to the right part 33 | // We leave pivotIndex as is, but we increment `i`. 34 | // This means pivotIndex will be at the first position, where value 10 is. 35 | // i is now 1 36 | // 6 is greater than 4, so it needs to be moved to the right part, hence we leave pivotIndex but increase the i index 37 | // i is now 2 38 | // 2 is less than 4, we hit our if condition 39 | // so now we swap 2 with 10, because pivotIndex is still at 0 40 | 41 | // this would continue on until we reach the end, it's important to note that the end is the pivot 42 | // because we're saying i less than end, end is the index of the pivot, not "length" in traditional sense 43 | // so at the end, we still have the pivot value 44 | // we swap the pivot value with the value at the pivotIndex, and we return the pivotIndex 45 | // pivotindex at the end will be the final position of the pivot 46 | // so everything to the left of the pivotIndex will be less than the pivot value 47 | // and everything to the right of the pivotIndex will be greater than the pivot value 48 | 49 | for (let i = start; i < end; i++) { 50 | // If an element is less than the pivot, it needs to be moved to the left part 51 | if (arr[i] < pivotValue) { 52 | // Traditional swapping using a temporary variable 53 | let temp = arr[i] 54 | arr[i] = arr[pivotIndex] 55 | arr[pivotIndex] = temp 56 | 57 | // Move pivotIndex to the right, marking a new position for smaller elements 58 | pivotIndex++ 59 | } 60 | } 61 | 62 | // Swap the pivot with the element at the pivotIndex, placing the pivot correctly 63 | let temp = arr[pivotIndex] 64 | arr[pivotIndex] = arr[end] 65 | arr[end] = temp 66 | 67 | return pivotIndex // Return the final position of the pivot 68 | } 69 | 70 | // Example usage 71 | let unsortedArray = [10, 6, 8, 5, 7, 3, 4] 72 | console.log(quickSort(unsortedArray)) // Output should be the sorted array 73 | -------------------------------------------------------------------------------- /src/leetcode/arrays/removeDuplicates-two.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var removeDuplicates = function (nums) { 6 | let L = 0 7 | let R = 2 8 | 9 | while (R < nums.length) { 10 | if (nums[L] === nums[L + 1] && nums[L + 1] === nums[R]) { 11 | nums.splice(R, 1) 12 | continue 13 | } 14 | 15 | L++ 16 | R++ 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/leetcode/arrays/removeDuplicates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var removeDuplicates = function (nums) { 6 | let R = 1 7 | let L = 0 8 | 9 | while (R < nums.length) { 10 | if (nums[L] === nums[R]) { 11 | nums.splice(R, 1) 12 | continue 13 | } 14 | 15 | L++ 16 | R++ 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/leetcode/arrays/searchMatrix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} matrix 3 | * @param {number} target 4 | * @return {boolean} 5 | */ 6 | var searchMatrix = function (matrix, target) { 7 | const ROWS = matrix.length 8 | const COLUMNS = matrix[0].length 9 | 10 | let top = 0 11 | let bottom = ROWS - 1 12 | while (top <= bottom) { 13 | const mid = Math.floor((bottom + top) / 2) 14 | 15 | if (target > matrix[mid][COLUMNS - 1]) { 16 | top = mid + 1 17 | } else if (target < matrix[mid][0]) { 18 | bottom = mid - 1 19 | } else { 20 | // If the target is within the range of the current row, break to search in this row. 21 | break 22 | } 23 | } 24 | 25 | // If the target row was not found, return false. 26 | if (top > bottom) { 27 | return false 28 | } 29 | 30 | // At this point, top is the row where the target might be. 31 | let row = Math.floor((bottom + top) / 2) 32 | 33 | let left = 0 34 | let right = COLUMNS - 1 35 | 36 | while (left <= right) { 37 | const mid = Math.floor((left + right) / 2) 38 | 39 | if (matrix[row][mid] > target) { 40 | right = mid - 1 41 | } else if (matrix[row][mid] < target) { 42 | left = mid + 1 43 | } else { 44 | return true // Return true since the target is found. 45 | } 46 | } 47 | 48 | return false // Target is not found in the matrix. 49 | } 50 | -------------------------------------------------------------------------------- /src/leetcode/arrays/sortColors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {void} Do not return anything, modify nums in-place instead. 4 | */ 5 | var sortColors = function (nums) { 6 | const counts = [0, 0, 0] 7 | 8 | for (let i = 0; i < nums.length; i++) { 9 | counts[nums[i]]++ 10 | } 11 | 12 | let i = 0 13 | for (let n = 0; n < counts.length; n++) { 14 | for (let j = 0; j < counts[n]; j++) { 15 | nums[i] = n 16 | i++ 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/leetcode/arrays/sumRange.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | */ 4 | var NumArray = function (nums) { 5 | this.prefix = [] 6 | this.total = 0 7 | 8 | for (let i = 0; i < nums.length; i++) { 9 | this.total += nums[i] 10 | this.prefix.push(this.total) 11 | } 12 | } 13 | 14 | /** 15 | * @param {number} left 16 | * @param {number} right 17 | * @return {number} 18 | */ 19 | NumArray.prototype.sumRange = function (left, right) { 20 | let preRight = this.prefix[right] 21 | let preLeft = left > 0 ? this.prefix[left - 1] : 0 22 | return preRight - preLeft 23 | } 24 | 25 | /** 26 | * Your NumArray object will be instantiated and called as such: 27 | * var obj = new NumArray(nums) 28 | * var param_1 = obj.sumRange(left,right) 29 | */ 30 | -------------------------------------------------------------------------------- /src/leetcode/arrays/sumRegion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} matrix 3 | */ 4 | var NumMatrix = function (matrix) { 5 | const ROWS = matrix.length 6 | const COLUMNS = matrix[0].length 7 | 8 | this.sum = Array(ROWS + 1) 9 | .fill(0) 10 | .map(() => Array(COLUMNS + 1).fill(0)) 11 | 12 | this.total = 0 13 | 14 | for (let row = 0; row < ROWS; row++) { 15 | let prefix = 0 16 | for (let col = 0; col < COLUMNS; col++) { 17 | prefix += matrix[row][col] 18 | let above = this.sum[row][col + 1] // Correction: Add the sum directly above the current cell 19 | this.sum[row + 1][col + 1] = above + prefix 20 | } 21 | } 22 | } 23 | 24 | /** 25 | * @param {number} row1 26 | * @param {number} col1 27 | * @param {number} row2 28 | * @param {number} col2 29 | * @return {number} 30 | */ 31 | NumMatrix.prototype.sumRegion = function (row1, col1, row2, col2) { 32 | let bottomRight = this.sum[row2 + 1][col2 + 1] 33 | let leftToSubtract = this.sum[row2 + 1][col1] // should subtract from col1, not add 34 | let topToSubtract = this.sum[row1][col2 + 1] // Correctly subtracting the top area 35 | let topLeftToAddBack = this.sum[row1][col1] // This is the area subtracted twice and needs to be added back 36 | 37 | return bottomRight - leftToSubtract - topToSubtract + topLeftToAddBack 38 | } 39 | 40 | /** 41 | * Your NumMatrix object will be instantiated and called as such: 42 | * var obj = new NumMatrix(matrix) 43 | * var param_1 = obj.sumRegion(row1,col1,row2,col2) 44 | */ 45 | -------------------------------------------------------------------------------- /src/leetcode/backtracking/combinationSum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} candidates 3 | * @param {number} target 4 | * @return {number[][]} 5 | */ 6 | var combinationSum = function (candidates, target) { 7 | let res = [] 8 | 9 | function dfs(i, cur, totalSum) { 10 | if (totalSum === target) { 11 | res.push(cur.slice()) 12 | return 13 | } 14 | 15 | if (i >= candidates.length || totalSum > target) { 16 | return 17 | } 18 | 19 | cur.push(candidates[i]) 20 | dfs(i, cur, totalSum + candidates[i]) 21 | 22 | cur.pop() 23 | dfs(i + 1, cur, totalSum) 24 | } 25 | 26 | dfs(0, [], 0) 27 | return res 28 | } 29 | -------------------------------------------------------------------------------- /src/leetcode/backtracking/hasPathSum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val, left, right) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.left = (left===undefined ? null : left) 6 | * this.right = (right===undefined ? null : right) 7 | * } 8 | */ 9 | /** 10 | * @param {TreeNode} root 11 | * @param {number} targetSum 12 | * @return {boolean} 13 | */ 14 | var hasPathSum = function (root, targetSum, totalSum = 0) { 15 | if (!root) return false 16 | 17 | totalSum += root.val 18 | 19 | if (!root.right && !root.left) { 20 | if (totalSum === targetSum) { 21 | return true 22 | } 23 | } 24 | 25 | if (hasPathSum(root.left, targetSum, totalSum)) return true 26 | 27 | if (hasPathSum(root.right, targetSum, totalSum)) return true 28 | 29 | totalSum -= root.val 30 | 31 | return false 32 | } 33 | -------------------------------------------------------------------------------- /src/leetcode/backtracking/subsets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[][]} 4 | */ 5 | var subsets = function (nums) { 6 | let res = [] 7 | let subset = [] 8 | 9 | function dfs(i) { 10 | if (i >= nums.length) { 11 | res.push(subset.slice()) 12 | return 13 | } 14 | 15 | subset.push(nums[i]) 16 | dfs(i + 1) 17 | 18 | subset.pop() 19 | dfs(i + 1) 20 | } 21 | 22 | dfs(0) 23 | return res 24 | } 25 | -------------------------------------------------------------------------------- /src/leetcode/bitwise/hammingWeight.js: -------------------------------------------------------------------------------- 1 | // /** 2 | // * @param {number} n - a positive integer 3 | // * @return {number} 4 | // */ 5 | // var hammingWeight = function (n) { 6 | // let count = 0 7 | 8 | // while (n > 0) { 9 | // // Use !== 0 to handle negative numbers correctly 10 | // if ((n & 1) === 1) { 11 | // count += 1 12 | // } 13 | 14 | // n = n >>> 1 // Use unsigned right shift 15 | // } 16 | 17 | // return count 18 | // } 19 | 20 | /** 21 | * @param {number} n - a positive integer 22 | * @return {number} 23 | */ 24 | var hammingWeight = function (n) { 25 | let count = 0 26 | 27 | while (n > 0) { 28 | // This solution uses the modulo operator 29 | // For example 101 % 2 = 1 -> The last digit is 1 30 | // 100 % 2 = 0 -> The last digit is 0 31 | count += n % 2 32 | n = n >>> 1 // Use unsigned right shift 33 | } 34 | 35 | return count 36 | } 37 | -------------------------------------------------------------------------------- /src/leetcode/dynamic-programming/fib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {number} 4 | */ 5 | // var fib = function (n, cache = {}) { 6 | // if (n < 2) return n 7 | 8 | // if (cache[n] !== undefined) { 9 | // return cache[n] 10 | // } 11 | 12 | // cache[n] = fib(n - 1) + fib(n - 2) 13 | 14 | // return cache[n] 15 | // } 16 | 17 | /** 18 | * @param {number} n 19 | * @return {number} 20 | */ 21 | var fib = function (n) { 22 | if (n < 2) return n 23 | 24 | let arr = [0, 1] 25 | let i = 2 26 | 27 | while (i <= n) { 28 | let temp = arr[1] 29 | arr[1] = arr[0] + arr[1] 30 | arr[0] = temp 31 | i++ 32 | } 33 | 34 | return arr[1] 35 | } 36 | -------------------------------------------------------------------------------- /src/leetcode/dynamic-programming/houseRobber.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var rob = function (nums) { 6 | let prev1 = nums[0] 7 | let prev2 = 0 8 | 9 | for (let i = 0; i < nums.length; i++) { 10 | if (i === 0) continue 11 | 12 | let temp = Math.max(prev2 + nums[i], prev1) 13 | 14 | prev2 = prev1 15 | prev1 = temp 16 | } 17 | 18 | return prev1 19 | } 20 | -------------------------------------------------------------------------------- /src/leetcode/dynamic-programming/longestCommonSubsequence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} text1 3 | * @param {string} text2 4 | * @return {number} 5 | */ 6 | var longestCommonSubsequence = function (text1, text2) { 7 | const ROWS = text1.length 8 | const COLUMNS = text2.length 9 | 10 | const dp = Array(ROWS + 1) 11 | .fill(0) 12 | .map(() => Array(COLUMNS + 1).fill(0)) 13 | 14 | for (let row = ROWS - 1; row >= 0; row--) { 15 | for (let col = COLUMNS - 1; col >= 0; col--) { 16 | if (text1[row] === text2[col]) { 17 | dp[row][col] = 1 + dp[row + 1][col + 1] 18 | } else { 19 | dp[row][col] = Math.max(dp[row][col + 1], dp[row + 1][col]) 20 | } 21 | } 22 | } 23 | 24 | return dp[0][0] 25 | } 26 | -------------------------------------------------------------------------------- /src/leetcode/dynamic-programming/uniquePaths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} m 3 | * @param {number} n 4 | * @return {number} 5 | */ 6 | var uniquePaths = function (rows, cols) { 7 | let prevRow = Array(cols).fill(0) 8 | 9 | for (let row = rows - 1; row >= 0; row--) { 10 | let currentRow = Array(cols).fill(0) 11 | currentRow[cols - 1] = 1 12 | 13 | for (let col = cols - 2; col >= 0; col--) { 14 | currentRow[col] = currentRow[col + 1] + prevRow[col] 15 | } 16 | 17 | prevRow = currentRow 18 | } 19 | 20 | return prevRow[0] 21 | } 22 | -------------------------------------------------------------------------------- /src/leetcode/dynamic-programming/uniquePathsWithObstacles.js: -------------------------------------------------------------------------------- 1 | // /** 2 | // * @param {number[][]} obstacleGrid 3 | // * @return {number} 4 | // */ 5 | // var uniquePathsWithObstacles = function (obstacleGrid) { 6 | // let row = 0 7 | // let col = 0 8 | // const ROWS = obstacleGrid.length 9 | // const COLUMNS = obstacleGrid[0].length 10 | // const cache = Array(ROWS) 11 | // .fill(0) 12 | // .map(() => Array(COLUMNS).fill(0)) 13 | // return dp(row, col, ROWS, COLUMNS, obstacleGrid, cache) 14 | // } 15 | 16 | // function dp(row, col, ROWS, COLUMNS, grid, cache) { 17 | // if (row >= ROWS || col >= COLUMNS) { 18 | // return 0 19 | // } 20 | 21 | // if (grid[row][col] === 1) { 22 | // return 0 23 | // } 24 | 25 | // if (row === ROWS - 1 && col === COLUMNS - 1) { 26 | // return 1 27 | // } 28 | 29 | // if (cache[row][col] !== 0) { 30 | // return cache[row][col] 31 | // } 32 | 33 | // cache[row][col] = 34 | // dp(row + 1, col, ROWS, COLUMNS, grid, cache) + 35 | // dp(row, col + 1, ROWS, COLUMNS, grid, cache) 36 | 37 | // return cache[row][col] 38 | // } 39 | 40 | function uniquePathsWithObstaclesBottomUp(obstacleGrid) { 41 | let ROWS = obstacleGrid.length 42 | let COLS = obstacleGrid[0].length 43 | let dp = Array(ROWS) 44 | .fill(0) 45 | .map(() => Array(COLS).fill(0)) 46 | 47 | for (let row = ROWS - 1; row >= 0; row--) { 48 | for (let col = COLS - 1; col >= 0; col--) { 49 | if (obstacleGrid[row][col] === 1) { 50 | // if obstacle, set to 0 51 | // because we can't go through obstacles 52 | // so there are no paths 53 | dp[row][col] = 0 54 | } else if (row === ROWS - 1 && col === COLS - 1) { 55 | // we start at the bottom right 56 | // so there's only one path 57 | dp[row][col] = 1 58 | } else { 59 | const isRowNotOutOfBounds = row + 1 < ROWS 60 | const isColNotOutOfBounds = col + 1 < COLS 61 | 62 | // if we're not at the bottom row 63 | // and not an obstacle 64 | // we calculate the number of paths 65 | // by adding the number of paths from the cell below 66 | // and the number of paths from the cell to the right 67 | 68 | // if out of bounds, nothing to add 69 | // so we set to 0 70 | let down = isRowNotOutOfBounds ? dp[row + 1][col] : 0 71 | let right = isColNotOutOfBounds ? dp[row][col + 1] : 0 72 | 73 | dp[row][col] = down + right 74 | } 75 | } 76 | } 77 | return dp[0][0] 78 | } 79 | -------------------------------------------------------------------------------- /src/leetcode/graphs/canFinish.js: -------------------------------------------------------------------------------- 1 | function canFinish(numCourses, prerequisites) { 2 | const preMap = new Map() 3 | // Initialize adjacency list 4 | for (let i = 0; i < numCourses; i++) { 5 | preMap.set(i, []) 6 | } 7 | for (const [course, preReq] of prerequisites) { 8 | preMap.get(course).push(preReq) 9 | } 10 | 11 | const visitedSet = new Set() 12 | const dfsVisited = new Set() // Tracks nodes in the current DFS path 13 | 14 | function dfs(course) { 15 | if (dfsVisited.has(course)) return false // Cycle detected 16 | if (visitedSet.has(course)) return true // Already processed, no cycle found previously 17 | 18 | dfsVisited.add(course) 19 | for (const preReq of preMap.get(course)) { 20 | if (!dfs(preReq)) return false // Cycle detected in a prerequisite 21 | } 22 | dfsVisited.delete(course) // Remove from current DFS path 23 | visitedSet.add(course) // Mark as processed globally 24 | 25 | return true 26 | } 27 | 28 | for (let i = 0; i < numCourses; i++) { 29 | if (!dfs(i)) return false 30 | } 31 | return true 32 | } 33 | -------------------------------------------------------------------------------- /src/leetcode/graphs/cloneGraph.js: -------------------------------------------------------------------------------- 1 | /** 2 | * // Definition for a Node. 3 | * function Node(val, neighbors) { 4 | * this.val = val === undefined ? 0 : val; 5 | * this.neighbors = neighbors === undefined ? [] : neighbors; 6 | * }; 7 | */ 8 | 9 | /** 10 | * @param {Node} node 11 | * @return {Node} 12 | */ 13 | var cloneGraph = function (node) { 14 | if (!node) return null 15 | 16 | const oldNodes = new Map() 17 | 18 | function dfs(oldNode) { 19 | if (oldNodes.has(oldNode)) { 20 | return oldNodes.get(oldNode) 21 | } 22 | 23 | const newNode = new Node(oldNode.val) 24 | oldNodes.set(oldNode, newNode) 25 | 26 | for (let i = 0; i < oldNode.neighbors.length; i++) { 27 | const neig = oldNode.neighbors[i] 28 | newNode.neighbors.push(dfs(neig)) 29 | } 30 | 31 | return newNode 32 | } 33 | 34 | return dfs(node) 35 | } 36 | -------------------------------------------------------------------------------- /src/leetcode/graphs/maxAreaOfIsland.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} grid 3 | * @return {number} 4 | */ 5 | var maxAreaOfIsland = function (grid) { 6 | let maxArea = 0 7 | 8 | for (let row = 0; row < grid.length; row++) { 9 | for (let column = 0; column < grid[0].length; column++) { 10 | if (grid[row][column] === 1) { 11 | const newArea = floodFill(grid, row, column) 12 | maxArea = Math.max(newArea, maxArea) 13 | } 14 | } 15 | } 16 | 17 | return maxArea 18 | } 19 | 20 | function floodFill(grid, row, column) { 21 | if (row < 0 || column < 0) { 22 | return 0 23 | } 24 | 25 | if (row >= grid.length || column >= grid[0].length) { 26 | return 0 27 | } 28 | 29 | if (grid[row][column] === 2 || grid[row][column] === 0) { 30 | return 0 31 | } 32 | 33 | grid[row][column] = 2 34 | 35 | let count = 1 36 | count += floodFill(grid, row - 1, column) 37 | count += floodFill(grid, row + 1, column) 38 | count += floodFill(grid, row, column + 1) 39 | count += floodFill(grid, row, column - 1) 40 | 41 | return count 42 | } 43 | -------------------------------------------------------------------------------- /src/leetcode/graphs/numIslands.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {character[][]} grid 3 | * @return {number} 4 | */ 5 | var numIslands = function (grid) { 6 | let count = 0 7 | 8 | const ROWS = grid.length 9 | const COLUMNS = grid[0].length 10 | 11 | for (let row = 0; row < ROWS; row++) { 12 | for (let column = 0; column < COLUMNS; column++) { 13 | if (grid[row][column] === '1') { 14 | count++ 15 | floodFill(grid, row, column) 16 | } 17 | } 18 | } 19 | 20 | return count 21 | } 22 | 23 | function floodFill(grid, row, column) { 24 | if (row < 0 || column < 0) { 25 | return 26 | } 27 | 28 | if (row >= grid.length || column >= grid[0].length) { 29 | return 30 | } 31 | 32 | if (grid[row][column] === '2' || grid[row][column] === '0') { 33 | return 34 | } 35 | 36 | grid[row][column] = '2' 37 | floodFill(grid, row + 1, column) 38 | floodFill(grid, row - 1, column) 39 | floodFill(grid, row, column + 1) 40 | floodFill(grid, row, column - 1) 41 | } 42 | -------------------------------------------------------------------------------- /src/leetcode/graphs/orangesRotting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} grid 3 | * @return {number} 4 | */ 5 | var orangesRotting = function (grid) { 6 | const ROWS = grid.length 7 | const COLUMNS = grid[0].length 8 | 9 | let time = 0 10 | let fresh = 0 11 | let queue = [] 12 | 13 | // Scanning the grid to find fresh and rotten oranges 14 | for (let row = 0; row < ROWS; row++) { 15 | for (let column = 0; column < COLUMNS; column++) { 16 | if (grid[row][column] === 1) { 17 | fresh += 1 18 | } 19 | if (grid[row][column] === 2) { 20 | queue.push([row, column]) 21 | } 22 | } 23 | } 24 | 25 | // Early return if no fresh oranges are present initially 26 | if (fresh === 0) { 27 | return 0 28 | } 29 | 30 | const directions = [ 31 | [1, 0], 32 | [0, 1], 33 | [0, -1], 34 | [-1, 0], 35 | ] 36 | 37 | while (queue.length > 0 && fresh > 0) { 38 | let length = queue.length 39 | for (let i = 0; i < length; i++) { 40 | const [row, column] = queue.shift() 41 | 42 | for (let j = 0; j < directions.length; j++) { 43 | const [rowDifference, columnDifference] = directions[j] 44 | const newRow = row + rowDifference 45 | const newColumn = column + columnDifference 46 | 47 | if ( 48 | newRow < 0 || 49 | newRow >= ROWS || 50 | newColumn < 0 || 51 | newColumn >= COLUMNS || 52 | grid[newRow][newColumn] !== 1 53 | ) { 54 | continue 55 | } 56 | 57 | grid[newRow][newColumn] = 2 // Mark as rotten 58 | fresh-- // Decrement the count of fresh oranges 59 | queue.push([newRow, newColumn]) // Add the newly rotten orange to the queue 60 | } 61 | } 62 | 63 | time++ // Increment time after processing all oranges at the current time 64 | } 65 | 66 | return fresh === 0 ? time : -1 67 | } 68 | -------------------------------------------------------------------------------- /src/leetcode/graphs/shortestPathBinaryMatrix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} grid 3 | * @return {number} 4 | */ 5 | var shortestPathBinaryMatrix = function (grid) { 6 | let ROWS = grid.length 7 | let COLUMNS = grid[0].length 8 | let visit = new Array(ROWS).fill(0).map(() => new Array(COLUMNS).fill(0)) 9 | let queue = [[0, 0, 1]] 10 | visit[0][0] = 1 11 | 12 | if (grid[0][0] === 1 || grid[ROWS - 1][COLUMNS - 1] === 1) { 13 | return -1 14 | } 15 | 16 | while (queue.length) { 17 | const length = queue.length 18 | for (let i = 0; i < length; i++) { 19 | const [row, col, pathLength] = queue.shift() 20 | 21 | if (row === ROWS - 1 && col === COLUMNS - 1) { 22 | return pathLength 23 | } 24 | 25 | const neighbours = [ 26 | [row + 1, col], 27 | [row - 1, col], 28 | [row, col + 1], 29 | [row, col - 1], 30 | [row + 1, col + 1], 31 | [row + 1, col - 1], 32 | [row - 1, col + 1], 33 | [row - 1, col - 1], 34 | ] 35 | 36 | for (let j = 0; j < neighbours.length; j++) { 37 | const [newRow, newCol] = neighbours[j] 38 | 39 | if ( 40 | Math.min(newRow, newCol) < 0 || 41 | newRow === ROWS || 42 | newCol === COLUMNS || 43 | visit[newRow][newCol] === 1 || 44 | grid[newRow][newCol] === 1 45 | ) { 46 | continue 47 | } 48 | 49 | queue.push([newRow, newCol, pathLength + 1]) 50 | visit[newRow][newCol] = 1 51 | } 52 | } 53 | } 54 | 55 | return -1 56 | } 57 | -------------------------------------------------------------------------------- /src/leetcode/hash/LRUCache.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(key, value) { 3 | this.key = key 4 | this.value = value 5 | this.prev = null 6 | this.next = null 7 | } 8 | } 9 | 10 | var LRUCache = function (capacity) { 11 | this.cache = {} 12 | this.cap = capacity 13 | this.length = 0 14 | this.left = new Node(0, 0) // Sentinel node for head 15 | this.right = new Node(0, 0) // Sentinel node for tail 16 | this.left.next = this.right 17 | this.right.prev = this.left 18 | 19 | // Inserts node at the right (Most Recently Used side) 20 | this.insert = function (node) { 21 | const prev = this.right.prev 22 | const next = this.right 23 | 24 | prev.next = node 25 | node.prev = prev 26 | 27 | node.next = next 28 | next.prev = node 29 | this.length++ // Increment length when a node is added 30 | } 31 | 32 | // Removes node from the list 33 | this.remove = function (node) { 34 | const prev = node.prev 35 | const next = node.next 36 | 37 | prev.next = next 38 | next.prev = prev 39 | 40 | this.length-- // Decrement length when a node is removed 41 | } 42 | } 43 | 44 | LRUCache.prototype.get = function (key) { 45 | if (this.cache[key] !== undefined) { 46 | this.remove(this.cache[key]) 47 | this.insert(this.cache[key]) 48 | return this.cache[key].value 49 | } 50 | 51 | return -1 52 | } 53 | 54 | LRUCache.prototype.put = function (key, value) { 55 | if (this.cache[key] !== undefined) { 56 | this.remove(this.cache[key]) 57 | } else { 58 | if (this.cap === this.length) { 59 | const lru = this.left.next 60 | this.remove(lru) 61 | delete this.cache[lru.key] 62 | } 63 | } 64 | 65 | const newNode = new Node(key, value) 66 | this.insert(newNode) 67 | this.cache[key] = newNode 68 | } 69 | -------------------------------------------------------------------------------- /src/leetcode/hash/containsDuplicate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {boolean} 4 | */ 5 | var containsDuplicate = function (nums) { 6 | const hash = {} 7 | 8 | for (let i = 0; i < nums.length; i++) { 9 | if (hash[nums[i]]) { 10 | return true 11 | } 12 | 13 | hash[nums[i]] = true 14 | } 15 | 16 | return false 17 | } 18 | -------------------------------------------------------------------------------- /src/leetcode/hash/hashMap.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(key, value) { 3 | this.value = value 4 | this.key = key 5 | this.next = null 6 | } 7 | } 8 | 9 | var MyHashMap = function () { 10 | this.size = 1000 11 | this.length = 0 12 | this.hashSet = Array.from({ length: this.size }, () => new Node(0, 0)) 13 | } 14 | 15 | MyHashMap.prototype.hash = function (key) { 16 | return key % this.hashSet.length 17 | } 18 | 19 | MyHashMap.prototype.rehash = function (key) { 20 | const oldSet = this.hashSet 21 | this.size *= 2 22 | this.length = 0 23 | this.hashSet = Array.from({ length: this.size }, () => new Node(0, 0)) 24 | 25 | for (let i = 0; i < oldSet.length; i++) { 26 | let cur = oldSet[i].next 27 | while (cur) { 28 | this.put(cur.key, cur.value) 29 | cur = cur.next 30 | } 31 | } 32 | } 33 | 34 | MyHashMap.prototype.put = function (key, value) { 35 | if (this.size / 2 === this.length) { 36 | this.rehash() 37 | } 38 | 39 | const index = this.hash(key) 40 | const newNode = new Node(key, value) 41 | 42 | let cur = this.hashSet[index] 43 | 44 | while (cur.next) { 45 | if (cur.next.key === key) { 46 | cur.next.value = value 47 | return 48 | } 49 | cur = cur.next 50 | } 51 | 52 | this.length++ 53 | cur.next = newNode 54 | } 55 | 56 | MyHashMap.prototype.remove = function (key) { 57 | const index = this.hash(key) 58 | let cur = this.hashSet[index] 59 | 60 | while (cur.next) { 61 | if (cur.next.key === key) { 62 | cur.next = cur.next.next 63 | this.length-- 64 | return 65 | } 66 | 67 | cur = cur.next 68 | } 69 | } 70 | 71 | MyHashMap.prototype.get = function (key) { 72 | const index = this.hash(key) 73 | let cur = this.hashSet[index] 74 | 75 | while (cur.next) { 76 | if (cur.next.key === key) { 77 | return cur.next.value 78 | } 79 | 80 | cur = cur.next 81 | } 82 | 83 | return -1 84 | } 85 | -------------------------------------------------------------------------------- /src/leetcode/hash/hashSet.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.next = null 5 | } 6 | } 7 | 8 | var MyHashSet = function () { 9 | this.size = 1000 10 | this.length = 0 11 | this.hashSet = Array.from({ length: this.size }, () => new Node(0)) 12 | } 13 | 14 | MyHashSet.prototype.hash = function (key) { 15 | return key % this.hashSet.length 16 | } 17 | 18 | MyHashSet.prototype.rehash = function (key) { 19 | const oldSet = this.hashSet 20 | this.size *= 2 21 | this.length = 0 22 | this.hashSet = Array.from({ length: this.size }, () => new Node(0)) 23 | 24 | for (let i = 0; i < oldSet.length; i++) { 25 | let cur = oldSet[i].next 26 | while (cur) { 27 | this.add(cur.value) 28 | cur = cur.next 29 | } 30 | } 31 | } 32 | 33 | MyHashSet.prototype.add = function (key) { 34 | if (this.size / 2 === this.length) { 35 | this.rehash() 36 | } 37 | 38 | const index = this.hash(key) 39 | const newNode = new Node(key) 40 | 41 | let cur = this.hashSet[index] 42 | 43 | while (cur.next) { 44 | if (cur.next.value === key) return 45 | cur = cur.next 46 | } 47 | 48 | this.length++ 49 | cur.next = newNode 50 | } 51 | 52 | MyHashSet.prototype.remove = function (key) { 53 | const index = this.hash(key) 54 | let cur = this.hashSet[index] 55 | 56 | while (cur.next) { 57 | if (cur.next.value === key) { 58 | cur.next = cur.next.next 59 | this.length-- 60 | return 61 | } 62 | 63 | cur = cur.next 64 | } 65 | } 66 | 67 | MyHashSet.prototype.contains = function (key) { 68 | const index = this.hash(key) 69 | let cur = this.hashSet[index] 70 | 71 | while (cur.next) { 72 | if (cur.next.value === key) { 73 | return true 74 | } 75 | 76 | cur = cur.next 77 | } 78 | 79 | return false 80 | } 81 | -------------------------------------------------------------------------------- /src/leetcode/hash/twoSum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} target 4 | * @return {number[]} 5 | */ 6 | var twoSum = function (nums, target) { 7 | const hash = {} 8 | 9 | for (let i = 0; i < nums.length; i++) { 10 | const difference = target - nums[i] 11 | 12 | if (hash[difference] !== undefined) { 13 | return [hash[difference], i] 14 | } 15 | 16 | hash[nums[i]] = i 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/leetcode/heap/kClosest.js: -------------------------------------------------------------------------------- 1 | import { MinPriorityQueue } from '@datastructures-js/priority-queue' 2 | 3 | /** 4 | * @param {number[][]} points 5 | * @param {number} k 6 | * @return {number[][]} 7 | */ 8 | var kClosest = function (points, k) { 9 | const queue = new MinPriorityQueue({ 10 | compare: (a, b) => a.priority - b.priority, 11 | }) 12 | 13 | for (let i = 0; i < points.length; i++) { 14 | const x = points[i][0] 15 | const y = points[i][1] 16 | 17 | const distance = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) 18 | queue.enqueue({ element: points[i], priority: distance }) 19 | } 20 | 21 | const result = [] 22 | 23 | for (let j = 0; j < k; j++) { 24 | result.push(queue.dequeue().element) 25 | } 26 | 27 | return result 28 | } 29 | -------------------------------------------------------------------------------- /src/leetcode/heap/kthLargest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} k 3 | * @param {number[]} nums 4 | */ 5 | var KthLargest = function (k, nums) { 6 | this.k = k 7 | this.nums = nums.sort((a, b) => a - b) 8 | } 9 | 10 | /** 11 | * @param {number} val 12 | * @return {number} 13 | */ 14 | KthLargest.prototype.add = function (val) { 15 | this.nums.push(val) 16 | this.nums.sort((a, b) => a - b) 17 | 18 | return this.nums[this.nums.length - this.k] 19 | } 20 | 21 | /** 22 | * Your KthLargest object will be instantiated and called as such: 23 | * var obj = new KthLargest(k, nums) 24 | * var param_1 = obj.add(val) 25 | */ 26 | -------------------------------------------------------------------------------- /src/leetcode/heap/lastStoneWeight.js: -------------------------------------------------------------------------------- 1 | import { MaxPriorityQueue } from '@datastructures-js/priority-queue' 2 | 3 | /** 4 | * @param {number[]} stones 5 | * @return {number} 6 | */ 7 | var lastStoneWeight = function (stones) { 8 | const stonesQueue = new MaxPriorityQueue() 9 | 10 | stones.forEach((stone) => stonesQueue.enqueue(stone)) 11 | 12 | while (stonesQueue.size() > 1) { 13 | const firstLargestStone = stonesQueue.dequeue().element // Access the element property 14 | const secondLargestStone = stonesQueue.dequeue().element // Access the element property 15 | 16 | if (firstLargestStone !== secondLargestStone) { 17 | const result = firstLargestStone - secondLargestStone 18 | stonesQueue.enqueue(result) 19 | } 20 | } 21 | 22 | return stonesQueue.isEmpty() ? 0 : stonesQueue.dequeue().element // Access the element property 23 | } 24 | -------------------------------------------------------------------------------- /src/leetcode/linked-lists/BrowserHistory.js: -------------------------------------------------------------------------------- 1 | class Site { 2 | constructor(site) { 3 | this.prev = null 4 | this.next = null 5 | this.value = site 6 | } 7 | } 8 | 9 | export class BrowserHistory { 10 | constructor(homepage) { 11 | const homeSite = new Site(homepage) 12 | this.history = homeSite 13 | this.current = homeSite 14 | } 15 | 16 | forward(steps) { 17 | let currentSite = this.current 18 | 19 | // if currentsite doesn't exist, then we won't continue the loop, meaning we went forward as much as we could 20 | while (currentSite) { 21 | // if steps is 0, then it means we went forward the amount we should've 22 | if (steps === 0) { 23 | break 24 | } 25 | 26 | currentSite = currentSite.next 27 | steps-- 28 | } 29 | 30 | this.current = currentSite 31 | return currentSite.value 32 | } 33 | 34 | visit(url) { 35 | const newSite = new Site(url) 36 | this.current.next = newSite 37 | newSite.prev = this.current 38 | this.current = newSite 39 | } 40 | 41 | back(steps) { 42 | let currentSite = this.current 43 | 44 | // if currentsite doesn't exist, then we won't continue the loop, meaning we went forward as much as we could 45 | while (currentSite) { 46 | // if steps is 0, then it means we went forward the amount we should've 47 | if (steps === 0) { 48 | break 49 | } 50 | 51 | currentSite = currentSite.prev 52 | steps-- 53 | } 54 | 55 | this.current = currentSite 56 | return currentSite.value 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/leetcode/linked-lists/MyLinkedList.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.val = value 4 | this.next = null 5 | this.prev = null 6 | } 7 | } 8 | 9 | export class MyLinkedList { 10 | constructor() { 11 | this.head = null 12 | this.length = 0 13 | this.tail = null 14 | } 15 | 16 | /** 17 | * @param {number} index 18 | * @return {number} 19 | */ 20 | get(index) { 21 | let currentIndex = 0 22 | let currentNode = this.head 23 | 24 | while (currentNode) { 25 | if (currentIndex === index) { 26 | return currentNode.val 27 | } 28 | 29 | currentIndex++ 30 | currentNode = currentNode.next 31 | } 32 | 33 | return -1 34 | } 35 | 36 | /** 37 | * @param {number} val 38 | * @return {void} 39 | */ 40 | addAtHead(val) { 41 | const newNode = new Node(val) 42 | this.length++ 43 | 44 | if (!this.head) { 45 | this.head = newNode 46 | this.tail = newNode 47 | return 48 | } 49 | 50 | newNode.next = this.head 51 | this.head.prev = newNode 52 | this.head = newNode 53 | } 54 | 55 | /** 56 | * @param {number} val 57 | * @return {void} 58 | */ 59 | addAtTail(val) { 60 | if (!this.head) { 61 | this.addAtHead(val) 62 | return 63 | } 64 | 65 | this.length++ 66 | const newNode = new Node(val) 67 | newNode.prev = this.tail 68 | this.tail.next = newNode 69 | this.tail = newNode 70 | } 71 | 72 | /** 73 | * @param {number} index 74 | * @param {number} val 75 | * @return {void} 76 | */ 77 | addAtIndex(index, val) { 78 | if (index === 0) { 79 | this.addAtHead(val) 80 | return 81 | } 82 | 83 | const newNode = new Node(val) 84 | let currentNode = this.head 85 | let currentIndex = 0 86 | 87 | while (currentNode) { 88 | if (currentIndex + 1 === index) { 89 | if (currentNode.next === null) { 90 | this.addAtTail(val) 91 | } else { 92 | this.length++ 93 | newNode.next = currentNode.next 94 | newNode.prev = currentNode 95 | currentNode.next.prev = newNode 96 | currentNode.next = newNode 97 | } 98 | return 99 | } 100 | 101 | currentNode = currentNode.next 102 | currentIndex++ 103 | } 104 | } 105 | 106 | /** 107 | * @param {number} index 108 | * @return {void} 109 | */ 110 | deleteAtIndex(index) { 111 | const isIndexOutOfBounds = index < 0 || index >= this.length 112 | if (isIndexOutOfBounds) { 113 | return -1 114 | } 115 | 116 | if (index === 0) { 117 | this.head = this.head.next 118 | if (this.head) { 119 | this.head.prev = null 120 | } else { 121 | this.tail = null 122 | } 123 | 124 | this.length-- 125 | return 126 | } 127 | 128 | let currentNode = this.head 129 | let currentIndex = 0 130 | 131 | while (currentNode) { 132 | // this is the node before the one we want to delete 133 | if (currentIndex + 1 === index) { 134 | // if the node we want to delete is the tail 135 | // then next of tail should be null 136 | // because currentNode.next is the tail 137 | const shouldDeleteTail = currentNode.next.next === null 138 | if (shouldDeleteTail) { 139 | currentNode.next = null 140 | this.tail = currentNode 141 | this.length-- 142 | return 143 | } else { 144 | currentNode.next = currentNode.next.next 145 | currentNode.next.prev = currentNode 146 | this.length-- 147 | return 148 | } 149 | } 150 | 151 | currentIndex++ 152 | currentNode = currentNode.next 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/leetcode/linked-lists/MyLinkedList.test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | import { MyLinkedList } from './MyLinkedList' 3 | 4 | describe('MyLinkedList', () => { 5 | it('initializes correctly', () => { 6 | const list = new MyLinkedList() 7 | expect(list.get(0)).toBe(-1) // List should be empty, so any get operation should return -1 8 | }) 9 | 10 | it('adds at head correctly', () => { 11 | const list = new MyLinkedList() 12 | list.addAtHead(1) 13 | expect(list.get(0)).toBe(1) // After adding at head, the first element should be 1 14 | list.addAtHead(2) 15 | expect(list.get(0)).toBe(2) // Adding another at head, it should now be the first 16 | expect(list.get(1)).toBe(1) // The previous head should now be second 17 | }) 18 | 19 | it('adds at tail correctly', () => { 20 | const list = new MyLinkedList() 21 | list.addAtTail(1) 22 | expect(list.get(0)).toBe(1) // After adding at tail, the first and only element should be 1 23 | list.addAtTail(2) 24 | expect(list.get(1)).toBe(2) // The new tail should be 2 25 | }) 26 | 27 | it('adds at index correctly', () => { 28 | const list = new MyLinkedList() 29 | list.addAtIndex(0, 1) // Equivalent to addAtHead when list is empty 30 | list.addAtIndex(1, 3) // Adding at tail since index equals list length 31 | list.addAtIndex(1, 2) // Adding in the middle 32 | expect(list.get(0)).toBe(1) 33 | expect(list.get(1)).toBe(2) 34 | expect(list.get(2)).toBe(3) 35 | }) 36 | 37 | it('gets value at index correctly', () => { 38 | const list = new MyLinkedList() 39 | list.addAtHead(1) 40 | list.addAtTail(2) 41 | expect(list.get(0)).toBe(1) 42 | expect(list.get(1)).toBe(2) 43 | expect(list.get(2)).toBe(-1) // Index out of bounds 44 | }) 45 | 46 | it('deletes at index correctly', () => { 47 | const list = new MyLinkedList() 48 | list.addAtHead(1) 49 | list.addAtTail(2) 50 | list.addAtTail(3) 51 | list.deleteAtIndex(1) // After deletion, list should be 1->3 52 | expect(list.get(1)).toBe(3) 53 | list.deleteAtIndex(0) // After deletion, list should be 3 54 | expect(list.get(0)).toBe(3) 55 | expect(list.get(1)).toBe(-1) // Now index 1 is out of bounds 56 | }) 57 | 58 | it('handles edge cases', () => { 59 | const list = new MyLinkedList() 60 | list.addAtHead(1) 61 | list.deleteAtIndex(0) // Delete the only element 62 | expect(list.get(0)).toBe(-1) // Now the list should be empty 63 | list.addAtTail(2) 64 | expect(list.get(0)).toBe(2) // The list should now have 2 as the only element 65 | list.addAtIndex(50, 3) // Should do nothing since index is out of bounds 66 | expect(list.get(1)).toBe(-1) 67 | list.addAtIndex(1, 3) // Should work since index is at the list's length 68 | expect(list.get(1)).toBe(3) 69 | }) 70 | 71 | it.only('handles specific sequence of operations without error', () => { 72 | const list = new MyLinkedList() 73 | list.addAtHead(2) // List now: [2] 74 | list.deleteAtIndex(1) // Attempt to delete at index 1, but list only has one item. No change. 75 | list.addAtHead(2) // List now: [2, 2] 76 | list.addAtHead(7) // List now: [7, 2, 2] 77 | list.addAtHead(3) // List now: [3, 7, 2, 2] 78 | list.addAtHead(2) // List now: [2, 3, 7, 2, 2] 79 | list.addAtHead(5) // List now: [5, 2, 3, 7, 2, 2] 80 | list.addAtTail(5) // List now: [5, 2, 3, 7, 2, 2, 5] 81 | expect(list.get(5)).toBe(2) // Should return the value at index 5 -> 2 82 | list.deleteAtIndex(6) // Deletes the last item, list now: [5, 2, 3, 7, 2, 2] 83 | list.deleteAtIndex(4) // Deletes the item at index 4, list now: [5, 2, 3, 7, 2] 84 | 85 | // Let's verify the final structure and order of the list is as expected 86 | expect(list.get(0)).toBe(5) 87 | expect(list.get(1)).toBe(2) 88 | expect(list.get(2)).toBe(3) 89 | expect(list.get(3)).toBe(7) 90 | expect(list.get(4)).toBe(2) // This is the last valid index now 91 | expect(list.get(5)).toBe(-1) // Since index 5 was deleted, it should return -1 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /src/leetcode/linked-lists/detectCycle.js: -------------------------------------------------------------------------------- 1 | var detectCycle = function (head) { 2 | let slow = head, 3 | fast = head, 4 | pointer = head 5 | 6 | // this uses the two pointer approach to detect a cycle in a linked list 7 | // the fast pointer moves twice as fast as the slow pointer 8 | // if there is a cycle, the two pointers will meet at some point 9 | while (fast !== null && fast.next !== null) { 10 | slow = slow.next 11 | fast = fast.next.next 12 | if (slow === fast) { 13 | // there is a cycle 14 | // we need to find the start of the cycle 15 | // we do so by moving the pointer to the head of the list 16 | // and moving the pointer and the slow pointer at the same pace 17 | // the point at which they meet is the start of the cycle 18 | // this is because the distance from the head to the start of the cycle 19 | // is the same as the distance from the point at which the two pointers meet 20 | while (pointer !== slow) { 21 | pointer = pointer.next 22 | slow = slow.next 23 | } 24 | return pointer 25 | } 26 | } 27 | return null 28 | } 29 | -------------------------------------------------------------------------------- /src/leetcode/linked-lists/hasCycle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | 9 | /** 10 | * @param {ListNode} head 11 | * @return {boolean} 12 | */ 13 | var hasCycle = function (head) { 14 | let fast = head 15 | let slow = head 16 | 17 | while (fast && fast.next) { 18 | fast = fast.next.next 19 | slow = slow.next 20 | 21 | if (fast === slow) { 22 | return true 23 | } 24 | } 25 | 26 | return false 27 | } 28 | -------------------------------------------------------------------------------- /src/leetcode/linked-lists/mergedTwoLists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val, next) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.next = (next===undefined ? null : next) 6 | * } 7 | */ 8 | 9 | // Input: (list1 = [1, 2, 4]), (list2 = [1, 3, 4]) 10 | // Output: [1, 1, 2, 3, 4, 4] 11 | 12 | /** 13 | * @param {ListNode} list1 14 | * @param {ListNode} list2 15 | * @return {ListNode} 16 | */ 17 | var mergeTwoLists = function (list1, list2) { 18 | let dummy = new ListNode() 19 | let tail = dummy 20 | 21 | while (list1 && list2) { 22 | if (list1.val < list2.val) { 23 | tail.next = list1 24 | list1 = list1.next 25 | } else { 26 | tail.next = list2 27 | list2 = list2.next 28 | } 29 | 30 | tail = tail.next 31 | } 32 | 33 | if (list1) { 34 | tail.next = list1 35 | } else { 36 | tail.next = list2 37 | } 38 | 39 | return dummy.next 40 | } 41 | -------------------------------------------------------------------------------- /src/leetcode/linked-lists/middleNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val, next) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.next = (next===undefined ? null : next) 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var middleNode = function (head) { 13 | let fast = head 14 | let slow = head 15 | 16 | while (fast && fast.next) { 17 | fast = fast.next.next 18 | slow = slow.next 19 | } 20 | 21 | return slow 22 | } 23 | -------------------------------------------------------------------------------- /src/leetcode/linked-lists/pairSum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val, next) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.next = (next===undefined ? null : next) 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {number} 11 | */ 12 | var pairSum = function (head) { 13 | let slow = head 14 | let fast = head 15 | 16 | let prev = null 17 | 18 | while (fast && fast.next) { 19 | fast = fast.next.next 20 | 21 | let tmp = slow.next 22 | slow.next = prev 23 | prev = slow 24 | slow = tmp 25 | } 26 | 27 | let res = 0 28 | 29 | while (slow) { 30 | res = Math.max(res, slow.val + prev.val) 31 | slow = slow.next 32 | prev = prev.next 33 | } 34 | 35 | return res 36 | } 37 | 38 | var pairSum = function (head) { 39 | let prev = null 40 | 41 | let slow = head 42 | let fast = head 43 | 44 | while (fast && fast.next) { 45 | fast = fast.next.next 46 | 47 | let tmp = slow.next 48 | slow.next = prev 49 | prev = slow 50 | slow = tmp 51 | } 52 | 53 | let res = 0 54 | 55 | while (slow) { 56 | res = Math.max(slow.val + prev.val) 57 | slow = slow.next 58 | prev = prev.next 59 | } 60 | 61 | return res 62 | } 63 | -------------------------------------------------------------------------------- /src/leetcode/linked-lists/reverseList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val, next) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.next = (next===undefined ? null : next) 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var reverseList = function (head) { 13 | let prev = null 14 | let current = head 15 | 16 | while (current !== null) { 17 | let temp = current.next 18 | current.next = prev 19 | prev = current 20 | current = temp 21 | } 22 | 23 | return prev 24 | } 25 | -------------------------------------------------------------------------------- /src/leetcode/stack/calPoints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} operations 3 | * @return {number} 4 | */ 5 | var calPoints = function (operations) { 6 | let score = [] 7 | 8 | let totalScore = 0 9 | 10 | for (let i = 0; i < operations.length; i++) { 11 | if (operations[i] === '+') { 12 | const newScore = score[score.length - 1] + score[score.length - 2] 13 | score.push(newScore) 14 | totalScore += newScore 15 | } else if (operations[i] === 'D') { 16 | const newScore = score[score.length - 1] * 2 17 | score.push(newScore) 18 | totalScore += newScore 19 | } else if (operations[i] === 'C') { 20 | const prevScore = score.pop() 21 | totalScore -= prevScore 22 | } else { 23 | const scoreInNumber = parseInt(operations[i]) 24 | totalScore += scoreInNumber 25 | score.push(scoreInNumber) 26 | } 27 | } 28 | 29 | return totalScore 30 | } 31 | -------------------------------------------------------------------------------- /src/leetcode/trees/WordDictionary.js: -------------------------------------------------------------------------------- 1 | class TrieNode { 2 | constructor() { 3 | this.children = {} 4 | this.isEndOfWord = false 5 | } 6 | } 7 | 8 | var WordDictionary = function () { 9 | this.root = new TrieNode() 10 | } 11 | 12 | /** 13 | * @param {string} word 14 | * @return {void} 15 | */ 16 | WordDictionary.prototype.addWord = function (word) { 17 | let curr = this.root 18 | 19 | for (let i = 0; i < word.length; i++) { 20 | let char = word[i] 21 | 22 | if (!curr.children[char]) { 23 | curr.children[char] = new TrieNode() 24 | } 25 | 26 | curr = curr.children[char] 27 | } 28 | 29 | curr.isEndOfWord = true 30 | } 31 | 32 | /** 33 | * @param {string} word 34 | * @return {boolean} 35 | */ 36 | WordDictionary.prototype.search = function (word) { 37 | function dfs(j, root) { 38 | let curr = root 39 | 40 | for (let i = j; i < word.length; i++) { 41 | let char = word[i] 42 | 43 | if (char === '.') { 44 | for (const child of Object.values(curr.children)) { 45 | if (dfs(i + 1, child)) { 46 | return true 47 | } 48 | } 49 | 50 | return false 51 | } else { 52 | if (!curr.children[char]) { 53 | return false 54 | } 55 | 56 | curr = curr.children[char] 57 | } 58 | } 59 | 60 | return curr.isEndOfWord 61 | } 62 | 63 | return dfs(0, this.root) 64 | } 65 | 66 | /** 67 | * Your WordDictionary object will be instantiated and called as such: 68 | * var obj = new WordDictionary() 69 | * obj.addWord(word) 70 | * var param_2 = obj.search(word) 71 | */ 72 | -------------------------------------------------------------------------------- /src/leetcode/trees/buildTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val, left, right) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.left = (left===undefined ? null : left) 6 | * this.right = (right===undefined ? null : right) 7 | * } 8 | */ 9 | /** 10 | * @param {number[]} preorder 11 | * @param {number[]} inorder 12 | * @return {TreeNode} 13 | */ 14 | var buildTree = function (preorder, inorder) { 15 | if (preorder.length === 0 || inorder.length === 0) { 16 | return null 17 | } 18 | 19 | let value = preorder[0] 20 | let root = new TreeNode(value) 21 | 22 | let mid = inorder.indexOf(value) 23 | 24 | root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid)) 25 | root.right = buildTree(preorder.slice(mid + 1), inorder.slice(mid + 1)) 26 | 27 | return root 28 | } 29 | -------------------------------------------------------------------------------- /src/leetcode/trees/deleteNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val, left, right) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.left = (left===undefined ? null : left) 6 | * this.right = (right===undefined ? null : right) 7 | * } 8 | */ 9 | /** 10 | * @param {TreeNode} root 11 | * @param {number} key 12 | * @return {TreeNode} 13 | */ 14 | var deleteNode = function (root, key) { 15 | if (!root) { 16 | return null 17 | } 18 | 19 | if (root.val > key) { 20 | root.left = deleteNode(root.left, key) 21 | } else if (root.val < key) { 22 | root.right = deleteNode(root.right, key) 23 | } else { 24 | if (!root.right) { 25 | return root.left 26 | } else if (!root.left) { 27 | return root.right 28 | } else { 29 | const minValue = minNodeValue(root.right) 30 | root.val = minValue.val 31 | root.right = deleteNode(root.right, minValue.val) 32 | } 33 | } 34 | } 35 | 36 | function minNodeValue(root) { 37 | let cur = root 38 | 39 | while (cur && cur.left) { 40 | cur = cur.left 41 | } 42 | 43 | return cur 44 | } 45 | -------------------------------------------------------------------------------- /src/leetcode/trees/inorderTraversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val, left, right) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.left = (left===undefined ? null : left) 6 | * this.right = (right===undefined ? null : right) 7 | * } 8 | */ 9 | /** 10 | * @param {TreeNode} root 11 | * @return {number[]} 12 | */ 13 | var inorderTraversal = function (root, res = []) { 14 | if (root) { 15 | inorderTraversal(root.left, res) 16 | res.push(root.val) 17 | inorderTraversal(root.right, res) 18 | } 19 | 20 | return res 21 | } 22 | -------------------------------------------------------------------------------- /src/leetcode/trees/insertIntoBST.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val, left, right) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.left = (left===undefined ? null : left) 6 | * this.right = (right===undefined ? null : right) 7 | * } 8 | */ 9 | /** 10 | * @param {TreeNode} root 11 | * @param {number} val 12 | * @return {TreeNode} 13 | */ 14 | var insertIntoBST = function (root, val) { 15 | if (!root) { 16 | return new TreeNode(val) 17 | } 18 | 19 | if (root.val > val) { 20 | root.left = insertIntoBST(root.left, val) 21 | } else { 22 | root.right = insertIntoBST(root.right, val) 23 | } 24 | 25 | return root 26 | } 27 | -------------------------------------------------------------------------------- /src/leetcode/trees/levelOrder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val, left, right) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.left = (left===undefined ? null : left) 6 | * this.right = (right===undefined ? null : right) 7 | * } 8 | */ 9 | /** 10 | * @param {TreeNode} root 11 | * @return {number[][]} 12 | */ 13 | var levelOrder = function (root) { 14 | if (!root) return [] 15 | 16 | const result = [] 17 | const queue = [root] 18 | 19 | while (queue.length) { 20 | let levelLength = queue.length 21 | const currentLevel = [] 22 | 23 | for (let i = 0; i < levelLength; i++) { 24 | const node = queue.shift() 25 | currentLevel.push(node.val) 26 | 27 | if (node.left) queue.push(node.left) 28 | if (node.right) queue.push(node.right) 29 | } 30 | 31 | result.push(currentLevel) 32 | } 33 | 34 | return result 35 | } 36 | -------------------------------------------------------------------------------- /src/leetcode/trees/rightSideView.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val, left, right) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.left = (left===undefined ? null : left) 6 | * this.right = (right===undefined ? null : right) 7 | * } 8 | */ 9 | /** 10 | * @param {TreeNode} root 11 | * @return {number[]} 12 | */ 13 | var rightSideView = function (root) { 14 | if (!root) return [] 15 | 16 | const result = [] 17 | const queue = [root] 18 | 19 | let level = 0 20 | 21 | let currentNodesForLevel = 1 22 | 23 | while (queue.length) { 24 | let levelLength = queue.length 25 | 26 | let rightMostNode = queue[currentNodesForLevel - 1] 27 | 28 | result.push(rightMostNode.val) 29 | 30 | currentNodesForLevel = 0 31 | 32 | for (let i = 0; i < levelLength; i++) { 33 | const node = queue.shift() 34 | 35 | if (node.left) { 36 | currentNodesForLevel++ 37 | queue.push(node.left) 38 | } 39 | 40 | if (node.right) { 41 | currentNodesForLevel++ 42 | queue.push(node.right) 43 | } 44 | } 45 | 46 | level += 1 47 | } 48 | 49 | return result 50 | } 51 | -------------------------------------------------------------------------------- /src/leetcode/trees/searchBST.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val, left, right) { 4 | * this.val = (val===undefined ? 0 : val) 5 | * this.left = (left===undefined ? null : left) 6 | * this.right = (right===undefined ? null : right) 7 | * } 8 | */ 9 | 10 | /** 11 | * @param {TreeNode} root 12 | * @param {number} val 13 | * @return {TreeNode} 14 | */ 15 | var searchBST = function (root, val) { 16 | if (!root) { 17 | return null 18 | } 19 | 20 | if (val > root.val) { 21 | return searchBST(root.right, val) 22 | } else if (val < root.val) { 23 | return searchBST(root.left, val) 24 | } else { 25 | return root 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/linked-lists/circular-linked-lists.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.next = null 5 | } 6 | } 7 | 8 | export class CircularLinkedList { 9 | constructor() { 10 | this.head = null 11 | this.tail = null 12 | this.length = 0 13 | } 14 | 15 | isEmpty() { 16 | return this.length === 0 17 | } 18 | 19 | append(value) { 20 | const newNode = new Node(value) 21 | 22 | if (this.head === null) { 23 | this.head = newNode 24 | this.tail = newNode 25 | this.tail.next = this.head 26 | } else { 27 | newNode.next = this.head 28 | this.tail.next = newNode 29 | this.tail = newNode 30 | } 31 | 32 | this.length++ 33 | } 34 | 35 | prepend(value) { 36 | const newNode = new Node(value) 37 | 38 | if (this.head === null) { 39 | this.head = newNode 40 | this.tail = this.head 41 | this.tail.next = this.head 42 | } else { 43 | this.tail.next = newNode 44 | newNode.next = this.head 45 | this.head = newNode 46 | } 47 | 48 | this.length++ 49 | } 50 | 51 | clear() { 52 | this.head = null 53 | this.tail = null 54 | this.length = 0 55 | } 56 | 57 | toArray() { 58 | if (this.head === null) return [] 59 | 60 | const array = [] 61 | let currentNode = this.head 62 | 63 | do { 64 | array.push(currentNode.value) 65 | currentNode = currentNode.next 66 | } while (currentNode !== this.head) 67 | 68 | return array 69 | } 70 | 71 | find(value) { 72 | if (this.head === null) return null 73 | 74 | let currentNode = this.head 75 | 76 | while (currentNode !== null) { 77 | if (currentNode.value === value) { 78 | return currentNode 79 | } 80 | 81 | currentNode = currentNode.next 82 | } 83 | 84 | return null 85 | } 86 | 87 | insert(index, value) { 88 | if (this.head === null) return null 89 | if (index === 0) { 90 | this.prepend(value) 91 | this.length++ 92 | return 93 | } 94 | 95 | const isIndexOutOfBounds = index < 0 || index > this.length 96 | 97 | if (isIndexOutOfBounds) return null 98 | 99 | let nodeBeforeIndexToBeInsertedAt = this.head 100 | 101 | for (let i = 0; i < index - 1; i++) { 102 | nodeBeforeIndexToBeInsertedAt = nodeBeforeIndexToBeInsertedAt.next 103 | } 104 | 105 | const newNode = new Node(value) 106 | newNode.next = nodeBeforeIndexToBeInsertedAt.next 107 | nodeBeforeIndexToBeInsertedAt.next = newNode 108 | this.length++ 109 | } 110 | 111 | removeAt(index) { 112 | if (this.head === null) return null 113 | 114 | const isIndexOutOfBounds = index < 0 || index > this.length 115 | if (isIndexOutOfBounds) return null 116 | 117 | if (index === 0) { 118 | return this.removeFirst() 119 | } 120 | 121 | let currentNode = this.head 122 | for (let i = 0; i < index - 1; i++) { 123 | currentNode = currentNode.next 124 | } 125 | 126 | const removed = currentNode.next 127 | currentNode.next = currentNode.next.next 128 | 129 | if (index === this.length - 1) { 130 | this.tail = currentNode 131 | } 132 | 133 | this.length-- 134 | return removed 135 | } 136 | 137 | removeFirst() { 138 | if (this.head === null) return null 139 | 140 | const removed = this.head 141 | this.head = this.head.next 142 | this.length-- 143 | 144 | return removed 145 | } 146 | 147 | removeLast() { 148 | if (this.head === null) { 149 | return null 150 | } 151 | 152 | let tail = this.head 153 | let nodeBeforeTail = null 154 | 155 | // This loop stops at the last node as current.next will be null for the tail 156 | while (tail !== this.tail) { 157 | nodeBeforeTail = tail 158 | tail = tail.next 159 | } 160 | 161 | nodeBeforeTail.next = this.head 162 | this.tail = nodeBeforeTail 163 | this.length-- 164 | 165 | return tail 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/linked-lists/circular-linked-lists.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { CircularLinkedList } from './circular-linked-lists' 3 | 4 | // Test for creating a new circular linked list 5 | it('should create an empty circular linked list', () => { 6 | const list = new CircularLinkedList() 7 | expect(list).toBeDefined() 8 | expect(list.isEmpty()).toBe(true) 9 | }) 10 | 11 | // Test for appending elements 12 | it('should append elements correctly', () => { 13 | const list = new CircularLinkedList() 14 | list.append(1) 15 | list.append(2) 16 | 17 | expect(list.toArray()).toEqual([1, 2]) 18 | expect(list.tail.next.value).toBe(list.head.value) 19 | }) 20 | 21 | // Test for prepending elements 22 | it('should prepend elements correctly', () => { 23 | const list = new CircularLinkedList() 24 | list.prepend(1) 25 | list.prepend(2) 26 | 27 | expect(list.toArray()).toEqual([2, 1]) 28 | expect(list.tail.next.value).toBe(list.head.value) // Tail should point to head 29 | }) 30 | 31 | // Test for removing elements 32 | it('should remove elements correctly', () => { 33 | const list = new CircularLinkedList() 34 | list.append(1) 35 | list.append(2) 36 | list.append(3) 37 | list.removeAt(1) // Remove '2' 38 | 39 | expect(list.toArray()).toEqual([1, 3]) 40 | expect(list.tail.next.value).toBe(list.head.value) // Tail should point to head 41 | }) 42 | 43 | // Test to ensure circular nature 44 | it('should retain circular structure after operations', () => { 45 | const list = new CircularLinkedList() 46 | list.append(1) 47 | list.append(2) 48 | list.removeLast() 49 | list.prepend(3) 50 | 51 | expect(list.toArray()).toEqual([3, 1]) 52 | expect(list.tail.next.value).toBe(list.head.value) // Tail should point to head 53 | }) 54 | -------------------------------------------------------------------------------- /src/linked-lists/detect-cycle.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.next = null 5 | } 6 | } 7 | 8 | export class SinglyLinkedList { 9 | constructor() { 10 | this.head = null 11 | } 12 | 13 | append(value) { 14 | let newNode = new Node(value) 15 | if (!this.head) { 16 | this.head = newNode 17 | return 18 | } 19 | let current = this.head 20 | while (current.next) { 21 | current = current.next 22 | } 23 | current.next = newNode 24 | } 25 | } 26 | 27 | // Floyd's Tortoise and Hare Algorithm 28 | // Time complexity: O(n) 29 | // It's designed to detect a cycle in a linked list 30 | // It uses two pointers, one slow and one fast 31 | // Fast one moves two nodes at a time, slow one moves one node at a time 32 | // Analogy: two runners running on a track, one faster than the other 33 | export function hasCycle(list) { 34 | let slow = list.head 35 | let fast = list.head 36 | 37 | while (fast && fast.next) { 38 | slow = slow.next 39 | fast = fast.next.next 40 | 41 | if (slow.value === fast.value) { 42 | return true 43 | } 44 | } 45 | 46 | return false 47 | } 48 | -------------------------------------------------------------------------------- /src/linked-lists/detect-cycle.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { SinglyLinkedList, hasCycle } from './detect-cycle' 3 | 4 | it('should detect a cycle in the linked list', () => { 5 | const list = new SinglyLinkedList() 6 | list.append(1) 7 | list.append(2) 8 | list.append(3) 9 | 10 | // Manually create a cycle for testing 11 | let current = list.head 12 | while (current.next) { 13 | current = current.next 14 | } 15 | // Creating a cycle by pointing the last node to the head 16 | current.next = list.head 17 | 18 | expect(hasCycle(list)).toBe(true) 19 | }) 20 | 21 | it('should return false for a linked list without a cycle', () => { 22 | const list = new SinglyLinkedList() 23 | list.append(1) 24 | list.append(2) 25 | list.append(3) 26 | 27 | expect(hasCycle(list)).toBe(false) 28 | }) 29 | -------------------------------------------------------------------------------- /src/linked-lists/doubly.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.next = null 5 | this.prev = null 6 | } 7 | } 8 | 9 | export class DoublyLinkedList { 10 | constructor() { 11 | this.head = null 12 | this.tail = null 13 | this.length = 0 14 | } 15 | 16 | append(value) { 17 | if (this.head === null) { 18 | this.head = new Node(value) 19 | this.tail = this.head 20 | this.length++ 21 | } else { 22 | const newNode = new Node(value) 23 | this.tail.next = newNode 24 | newNode.prev = this.tail 25 | newNode.next = null 26 | this.tail = newNode 27 | this.length++ 28 | } 29 | } 30 | 31 | prepend(value) { 32 | if (this.head === null) { 33 | this.head = new Node(value) 34 | this.tail = this.head 35 | this.length++ 36 | } else { 37 | const newNode = new Node(value) 38 | this.head.prev = newNode 39 | newNode.next = this.head 40 | this.head = newNode 41 | this.length++ 42 | } 43 | } 44 | 45 | removeLast() { 46 | if (this.head === null) { 47 | return null 48 | } else if (this.head.value === this.tail.value) { 49 | const removed = this.tail 50 | this.head = null 51 | this.tail = null 52 | this.length-- 53 | return removed 54 | } else { 55 | const removed = this.tail 56 | this.tail.prev.next = null 57 | this.tail = this.tail.prev 58 | this.length-- 59 | 60 | return removed 61 | } 62 | } 63 | 64 | removeFirst() { 65 | if (this.head === null) { 66 | return null 67 | } else if (this.head.value === this.tail.value) { 68 | const removed = this.tail 69 | this.head = null 70 | this.tail = null 71 | this.length-- 72 | return removed 73 | } else { 74 | const removed = this.head 75 | this.head.next.prev = null 76 | this.head = this.head.next 77 | this.length-- 78 | 79 | return removed 80 | } 81 | } 82 | 83 | insert(index, value) { 84 | if (this.head === null) { 85 | return null 86 | } 87 | 88 | const isIndexOutOfBounds = index < 0 || index > this.length 89 | 90 | if (isIndexOutOfBounds) { 91 | return null 92 | } 93 | 94 | let nodeBeforeIndex = this.head 95 | for (let i = 0; i < index - 1; i++) { 96 | nodeBeforeIndex = nodeBeforeIndex.next 97 | } 98 | 99 | const newNode = new Node(value) 100 | 101 | nodeBeforeIndex.next.prev = newNode 102 | newNode.next = nodeBeforeIndex.next.prev 103 | 104 | nodeBeforeIndex.next = newNode 105 | newNode.prev = nodeBeforeIndex 106 | 107 | this.length++ 108 | } 109 | 110 | removeAt(index) { 111 | if (this.head === null) { 112 | return null 113 | } 114 | 115 | const isIndexOutOfBounds = index < 0 || index > this.length 116 | 117 | if (isIndexOutOfBounds) { 118 | return null 119 | } 120 | 121 | let nodeBeforeIndex = this.head 122 | for (let i = 0; i < index - 1; i++) { 123 | nodeBeforeIndex = nodeBeforeIndex.next 124 | } 125 | 126 | const removed = nodeBeforeIndex.next 127 | 128 | nodeBeforeIndex.next.next.prev = nodeBeforeIndex 129 | nodeBeforeIndex.next = nodeBeforeIndex.next.next 130 | 131 | this.length-- 132 | return removed 133 | } 134 | 135 | find(value) { 136 | if (this.head === null) return null 137 | 138 | let currentNode = this.head 139 | 140 | while (currentNode !== null) { 141 | if (currentNode.value === value) { 142 | return currentNode 143 | } 144 | 145 | currentNode = currentNode.next 146 | } 147 | 148 | return null 149 | } 150 | 151 | toArray() { 152 | if (this.head === null) return [] 153 | 154 | let array = [] 155 | 156 | let currentNode = this.head 157 | 158 | while (currentNode !== null) { 159 | array.push(currentNode.value) 160 | currentNode = currentNode.next 161 | } 162 | 163 | return array 164 | } 165 | 166 | clear() { 167 | this.head = null 168 | this.tail = null 169 | this.length = 0 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/linked-lists/doubly.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { DoublyLinkedList } from './doubly' 3 | import { it, expect } from 'vitest' 4 | import { DoublyLinkedList } from './doubly' 5 | 6 | // Test for creating a new list 7 | it('should create a new list', () => { 8 | const list = new DoublyLinkedList() 9 | expect(list.head).toBe(null) 10 | expect(list.tail).toBe(null) 11 | expect(list.length).toBe(0) 12 | }) 13 | 14 | // Test for appending an element 15 | it('should append an element to the list', () => { 16 | const list = new DoublyLinkedList() 17 | list.append(1) 18 | expect(list.head.value).toBe(1) 19 | expect(list.tail.value).toBe(1) 20 | expect(list.length).toBe(1) 21 | list.append(2) 22 | expect(list.tail.value).toBe(2) 23 | expect(list.tail.prev.value).toBe(1) 24 | expect(list.length).toBe(2) 25 | }) 26 | 27 | // Test for prepending an element 28 | it('should prepend an element to the list', () => { 29 | const list = new DoublyLinkedList() 30 | list.prepend(1) 31 | expect(list.head.value).toBe(1) 32 | expect(list.tail.value).toBe(1) 33 | expect(list.length).toBe(1) 34 | list.prepend(0) 35 | expect(list.head.value).toBe(0) 36 | expect(list.head.next.value).toBe(1) 37 | expect(list.length).toBe(2) 38 | }) 39 | 40 | // Test for removing an element from the end 41 | it('should remove an element from the end of the list', () => { 42 | const list = new DoublyLinkedList() 43 | list.append(1) 44 | list.append(2) 45 | const removed = list.removeLast() 46 | expect(removed.value).toBe(2) 47 | expect(list.tail.value).toBe(1) 48 | expect(list.length).toBe(1) 49 | }) 50 | 51 | // Test for removing an element from the beginning 52 | it('should remove an element from the beginning of the list', () => { 53 | const list = new DoublyLinkedList() 54 | list.append(1) 55 | list.append(2) 56 | const removed = list.removeFirst() 57 | expect(removed.value).toBe(1) 58 | expect(list.head.value).toBe(2) 59 | expect(list.length).toBe(1) 60 | }) 61 | 62 | // Test for inserting an element at a specific index 63 | it('should insert an element at a specific index', () => { 64 | const list = new DoublyLinkedList() 65 | list.append(1) 66 | list.append(3) 67 | list.insert(1, 2) // Inserting 2 at index 1 68 | expect(list.head.next.value).toBe(2) 69 | expect(list.length).toBe(3) 70 | }) 71 | 72 | // Test for removing an element by index 73 | it('should remove an element by index', () => { 74 | const list = new DoublyLinkedList() 75 | list.append(1) 76 | list.append(2) 77 | list.append(3) 78 | const removed = list.removeAt(1) // Removing element at index 1 79 | expect(removed.value).toBe(2) 80 | expect(list.length).toBe(2) 81 | }) 82 | 83 | // Test for finding an element 84 | it('should find an element in the list', () => { 85 | const list = new DoublyLinkedList() 86 | list.append(1) 87 | list.append(2) 88 | list.append(3) 89 | const found = list.find(2) 90 | expect(found.value).toBe(2) 91 | }) 92 | 93 | // Test for converting the list to an array 94 | it('should convert the list to an array', () => { 95 | const list = new DoublyLinkedList() 96 | list.append(1) 97 | list.append(2) 98 | list.append(3) 99 | const array = list.toArray() 100 | expect(array).toEqual([1, 2, 3]) 101 | }) 102 | 103 | // Test for clearing the list 104 | it('should clear the list', () => { 105 | const list = new DoublyLinkedList() 106 | list.append(1) 107 | list.append(2) 108 | list.clear() 109 | expect(list.head).toBe(null) 110 | expect(list.tail).toBe(null) 111 | expect(list.length).toBe(0) 112 | }) 113 | -------------------------------------------------------------------------------- /src/linked-lists/merge-two-sorted.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.next = null 5 | } 6 | } 7 | 8 | export class SinglyLinkedList { 9 | constructor() { 10 | this.head = null 11 | } 12 | 13 | append(value) { 14 | let newNode = new Node(value) 15 | if (!this.head) { 16 | this.head = newNode 17 | return 18 | } 19 | let current = this.head 20 | while (current.next) { 21 | current = current.next 22 | } 23 | current.next = newNode 24 | } 25 | } 26 | 27 | export function mergeSortedLists(list1, list2) { 28 | let mergedList = new SinglyLinkedList() 29 | 30 | let firstHead = list1.head 31 | let secondHead = list2.head 32 | 33 | if (!firstHead) return list2 34 | if (!secondHead) return list1 35 | 36 | while (firstHead !== null && secondHead !== null) { 37 | if (firstHead.value < secondHead.value) { 38 | mergedList.append(firstHead.value) 39 | firstHead = firstHead.next 40 | } else { 41 | mergedList.append(secondHead.value) 42 | secondHead = secondHead.next 43 | } 44 | } 45 | 46 | // Append remaining elements of the first list, if any 47 | while (firstHead !== null) { 48 | mergedList.append(firstHead.value) 49 | firstHead = firstHead.next 50 | } 51 | 52 | // Append remaining elements of the second list, if any 53 | while (secondHead !== null) { 54 | mergedList.append(secondHead.value) 55 | secondHead = secondHead.next 56 | } 57 | 58 | return mergedList 59 | } 60 | -------------------------------------------------------------------------------- /src/linked-lists/merged-two-sorted.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { SinglyLinkedList, mergeSortedLists } from './merge-two-sorted' 3 | 4 | // Helper function to convert a linked list to an array for easy comparison 5 | function linkedListToArray(linkedList) { 6 | const array = [] 7 | let current = linkedList.head 8 | while (current) { 9 | array.push(current.value) 10 | current = current.next 11 | } 12 | return array 13 | } 14 | 15 | it('should merge two sorted lists into one sorted list', () => { 16 | const list1 = new SinglyLinkedList() 17 | list1.append(1) 18 | list1.append(3) 19 | list1.append(5) 20 | 21 | const list2 = new SinglyLinkedList() 22 | list2.append(2) 23 | list2.append(4) 24 | list2.append(6) 25 | 26 | const mergedList = mergeSortedLists(list1, list2) 27 | expect(linkedListToArray(mergedList)).toEqual([1, 2, 3, 4, 5, 6]) 28 | }) 29 | 30 | it('should handle one empty list correctly', () => { 31 | const list1 = new SinglyLinkedList() 32 | const list2 = new SinglyLinkedList() 33 | list2.append(1) 34 | list2.append(3) 35 | list2.append(5) 36 | 37 | const mergedList = mergeSortedLists(list1, list2) 38 | expect(linkedListToArray(mergedList)).toEqual([1, 3, 5]) 39 | }) 40 | 41 | it('should handle both empty lists correctly', () => { 42 | const list1 = new SinglyLinkedList() 43 | const list2 = new SinglyLinkedList() 44 | 45 | const mergedList = mergeSortedLists(list1, list2) 46 | expect(linkedListToArray(mergedList)).toEqual([]) 47 | }) 48 | -------------------------------------------------------------------------------- /src/linked-lists/reverse-singly.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.next = null 5 | } 6 | } 7 | 8 | export class SinglyLinkedList { 9 | constructor() { 10 | this.head = null 11 | } 12 | 13 | append(value) { 14 | let newNode = new Node(value) 15 | if (!this.head) { 16 | this.head = newNode 17 | return 18 | } 19 | let current = this.head 20 | while (current.next) { 21 | current = current.next 22 | } 23 | current.next = newNode 24 | } 25 | } 26 | 27 | export function reverseLinkedList(list) { 28 | let prevNode = null 29 | let currentNode = list.head 30 | 31 | while (currentNode) { 32 | const nextNode = currentNode.next 33 | currentNode.next = prevNode 34 | 35 | prevNode = currentNode 36 | currentNode = nextNode 37 | } 38 | 39 | list.head = prevNode 40 | } 41 | -------------------------------------------------------------------------------- /src/linked-lists/reverse-singly.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { SinglyLinkedList, reverseLinkedList } from './reverse-singly' 3 | 4 | import { it, expect } from 'vitest' 5 | import { SinglyLinkedList, reverseLinkedList } from './reverse-singly' 6 | 7 | it('should reverse the singly linked list', () => { 8 | const list = new SinglyLinkedList() 9 | list.append(1) 10 | list.append(2) 11 | list.append(3) 12 | 13 | reverseLinkedList(list) 14 | 15 | const reversedArray = [] 16 | let current = list.head 17 | while (current) { 18 | reversedArray.push(current.value) 19 | current = current.next 20 | } 21 | 22 | expect(reversedArray).toEqual([3, 2, 1]) 23 | }) 24 | -------------------------------------------------------------------------------- /src/linked-lists/singly.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.next = null 5 | } 6 | } 7 | 8 | export class SinglyLinkedList { 9 | constructor() { 10 | this.head = null 11 | this.tail = null 12 | this.length = 0 13 | } 14 | 15 | append(value) { 16 | if (this.head === null) { 17 | this.head = new Node(value) 18 | this.tail = this.head 19 | } else { 20 | const newNode = new Node(value) 21 | this.tail.next = newNode 22 | this.tail = newNode 23 | } 24 | 25 | this.length++ 26 | } 27 | 28 | prepend(value) { 29 | if (this.head === null) { 30 | this.head = new Node(value) 31 | this.tail = this.head 32 | } else { 33 | const node = new Node(value) 34 | node.next = this.head 35 | this.head = node 36 | } 37 | 38 | this.length++ 39 | } 40 | 41 | clear() { 42 | this.head = null 43 | this.tail = null 44 | this.length = 0 45 | } 46 | 47 | toArray() { 48 | if (this.head === null) return [] 49 | 50 | const array = [] 51 | 52 | let currentNode = this.head 53 | 54 | while (currentNode !== null) { 55 | array.push(currentNode.value) 56 | currentNode = currentNode.next 57 | } 58 | 59 | return array 60 | } 61 | 62 | find(value) { 63 | if (this.head === null) return null 64 | 65 | let currentNode = this.head 66 | 67 | while (currentNode !== null) { 68 | if (currentNode.value === value) { 69 | return currentNode 70 | } 71 | 72 | currentNode = currentNode.next 73 | } 74 | 75 | return null 76 | } 77 | 78 | insert(index, value) { 79 | if (this.head === null) return null 80 | if (index === 0) { 81 | this.prepend(value) 82 | this.length++ 83 | return 84 | } 85 | 86 | const isIndexOutOfBounds = index < 0 || index > this.length 87 | 88 | if (isIndexOutOfBounds) return null 89 | 90 | let nodeBeforeIndexToBeInsertedAt = this.head 91 | 92 | for (let i = 0; i < index - 1; i++) { 93 | nodeBeforeIndexToBeInsertedAt = nodeBeforeIndexToBeInsertedAt.next 94 | } 95 | 96 | const newNode = new Node(value) 97 | newNode.next = nodeBeforeIndexToBeInsertedAt.next 98 | nodeBeforeIndexToBeInsertedAt.next = newNode 99 | this.length++ 100 | } 101 | 102 | removeAt(index) { 103 | if (this.head === null) return null 104 | 105 | const isIndexOutOfBounds = index < 0 || index > this.length 106 | if (isIndexOutOfBounds) return null 107 | 108 | if (index === 0) { 109 | return this.removeFirst() 110 | } 111 | 112 | let currentNode = this.head 113 | for (let i = 0; i < index - 1; i++) { 114 | currentNode = currentNode.next 115 | } 116 | 117 | const removed = currentNode.next 118 | currentNode.next = currentNode.next.next 119 | 120 | if (index === this.length - 1) { 121 | this.tail = currentNode 122 | } 123 | 124 | this.length-- 125 | return removed 126 | } 127 | 128 | removeFirst() { 129 | if (this.head === null) return null 130 | 131 | const removed = this.head 132 | this.head = this.head.next 133 | this.length-- 134 | 135 | return removed 136 | } 137 | 138 | removeLast() { 139 | if (this.head === null) { 140 | return null 141 | } 142 | 143 | let tail = this.head 144 | let nodeBeforeTail = null 145 | 146 | // This loop stops at the last node as current.next will be null for the tail 147 | while (tail.next !== null) { 148 | nodeBeforeTail = tail 149 | tail = tail.next 150 | } 151 | 152 | nodeBeforeTail.next = null 153 | this.tail = nodeBeforeTail 154 | this.length-- 155 | 156 | return tail 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/linked-lists/singly.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { SinglyLinkedList } from './singly' 3 | 4 | import { it, expect } from 'vitest' 5 | import { SinglyLinkedList } from './singly' 6 | 7 | // Test for creating a new list 8 | it('should create a new list', () => { 9 | const list = new SinglyLinkedList() 10 | expect(list.head).toBe(null) 11 | expect(list.tail).toBe(null) 12 | expect(list.length).toBe(0) 13 | }) 14 | 15 | // Test for appending an element 16 | it('should append an element to the list', () => { 17 | const list = new SinglyLinkedList() 18 | list.append(1) 19 | expect(list.head.value).toBe(1) 20 | expect(list.tail.value).toBe(1) 21 | expect(list.length).toBe(1) 22 | }) 23 | 24 | // Test for prepending an element 25 | it('should prepend an element to the list', () => { 26 | const list = new SinglyLinkedList() 27 | list.prepend(1) 28 | expect(list.head.value).toBe(1) 29 | expect(list.tail.value).toBe(1) 30 | expect(list.length).toBe(1) 31 | list.prepend(0) 32 | expect(list.head.value).toBe(0) 33 | expect(list.tail.value).toBe(1) 34 | expect(list.length).toBe(2) 35 | }) 36 | 37 | // Test for removing an element from the end 38 | it('should remove an element from the end of the list', () => { 39 | const list = new SinglyLinkedList() 40 | list.append(1) 41 | list.append(2) 42 | const removed = list.removeLast() 43 | expect(removed.value).toBe(2) 44 | expect(list.tail.value).toBe(1) 45 | expect(list.length).toBe(1) 46 | }) 47 | 48 | // Test for removing an element from the beginning 49 | it('should remove an element from the beginning of the list', () => { 50 | const list = new SinglyLinkedList() 51 | list.append(1) 52 | list.append(2) 53 | const removed = list.removeFirst() 54 | expect(removed.value).toBe(1) 55 | expect(list.head.value).toBe(2) 56 | expect(list.length).toBe(1) 57 | }) 58 | 59 | // Test for inserting an element at a specific index 60 | it('should insert an element at a specific index', () => { 61 | const list = new SinglyLinkedList() 62 | list.append(1) 63 | list.append(3) 64 | list.insert(1, 2) // Inserting 2 at index 1 65 | expect(list.head.next.value).toBe(2) 66 | expect(list.length).toBe(3) 67 | }) 68 | 69 | // Test for removing an element by index 70 | it('should remove an element by index', () => { 71 | const list = new SinglyLinkedList() 72 | list.append(1) 73 | list.append(2) 74 | list.append(3) 75 | const removed = list.removeAt(1) // Removing element at index 1 76 | expect(removed.value).toBe(2) 77 | expect(list.length).toBe(2) 78 | }) 79 | 80 | // Test for finding an element 81 | it('should find an element in the list', () => { 82 | const list = new SinglyLinkedList() 83 | list.append(1) 84 | list.append(2) 85 | list.append(3) 86 | const found = list.find(2) 87 | expect(found.value).toBe(2) 88 | }) 89 | 90 | // Test for finding an element that does not exist 91 | it('should not find an element that does not exist', () => { 92 | const list = new SinglyLinkedList() 93 | list.append(1) 94 | list.append(2) 95 | const found = list.find(3) 96 | expect(found).toBe(null) 97 | }) 98 | 99 | // Test for converting the list to an array 100 | it('should convert the list to an array', () => { 101 | const list = new SinglyLinkedList() 102 | list.append(1) 103 | list.append(2) 104 | list.append(3) 105 | const array = list.toArray() 106 | expect(array).toEqual([1, 2, 3]) 107 | }) 108 | 109 | // Test for clearing the list 110 | it('should clear the list', () => { 111 | const list = new SinglyLinkedList() 112 | list.append(1) 113 | list.append(2) 114 | list.clear() 115 | expect(list.head).toBe(null) 116 | expect(list.tail).toBe(null) 117 | expect(list.length).toBe(0) 118 | }) 119 | -------------------------------------------------------------------------------- /src/queues/MinPriorityQueue.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value, priority) { 3 | this.value = value 4 | this.priority = priority 5 | } 6 | } 7 | 8 | export class MinPriorityQueue { 9 | constructor() { 10 | this.heap = [] 11 | } 12 | 13 | isEmpty() { 14 | return this.heap.length === 0 15 | } 16 | 17 | peek() { 18 | if (this.isEmpty()) { 19 | return null 20 | } 21 | 22 | return this.heap[0].value 23 | } 24 | 25 | #swap(index1, index2) { 26 | let temp = this.heap[index1] 27 | this.heap[index1] = this.heap[index2] 28 | this.heap[index2] = temp 29 | } 30 | 31 | #getParentIndex(childIndex) { 32 | return Math.floor((childIndex - 1) / 2) 33 | } 34 | 35 | #bubbleUp(indexOfInsertedNode = this.heap.length - 1) { 36 | let indexOfParentNode = this.#getParentIndex(indexOfInsertedNode) 37 | 38 | const isNotFirstElement = indexOfInsertedNode > 0 39 | while (true && isNotFirstElement) { 40 | const valueOfInsertedNode = this.heap[indexOfInsertedNode] 41 | const valueOfParentNode = this.heap[indexOfParentNode] 42 | 43 | const areWeAtTheEnd = !valueOfParentNode 44 | if (areWeAtTheEnd) { 45 | break 46 | } 47 | 48 | // Less priority value means higher priority 49 | // This is how a MinPriorityQueue works 50 | const hasChildHigherPriority = 51 | valueOfParentNode.priority > valueOfInsertedNode.priority 52 | 53 | if (hasChildHigherPriority) { 54 | this.#swap(indexOfInsertedNode, indexOfParentNode) 55 | 56 | let tempIndex = indexOfParentNode 57 | indexOfInsertedNode = indexOfParentNode 58 | indexOfParentNode = this.#getParentIndex(tempIndex) 59 | 60 | continue 61 | } 62 | 63 | break 64 | } 65 | } 66 | 67 | enqueue(value, priority) { 68 | const newNode = new Node(value, priority) 69 | 70 | this.heap.push(newNode) 71 | this.#bubbleUp() 72 | } 73 | 74 | #getLeftChildIndex(parentIndex) { 75 | return 2 * parentIndex + 1 76 | } 77 | 78 | #getRightChildIndex(parentIndex) { 79 | return 2 * parentIndex + 2 80 | } 81 | 82 | #bubbleDown(indexToBubbleDown = 0) { 83 | while (true) { 84 | const leftChildIndex = this.#getLeftChildIndex(indexToBubbleDown) 85 | const rightChildIndex = this.#getRightChildIndex(indexToBubbleDown) 86 | 87 | const currentNodePriority = this.heap[indexToBubbleDown]?.priority 88 | const leftChildPriority = this.heap[leftChildIndex]?.priority 89 | const rightChildPriority = this.heap[rightChildIndex]?.priority 90 | 91 | const isCurrentNodeLessPriorityThanBothChildren = 92 | currentNodePriority && 93 | leftChildPriority && 94 | rightChildPriority && 95 | currentNodePriority > leftChildPriority && 96 | currentNodePriority > rightChildPriority 97 | 98 | if (isCurrentNodeLessPriorityThanBothChildren) { 99 | if (leftChildPriority < rightChildPriority) { 100 | this.#swap(leftChildIndex, indexToBubbleDown) 101 | indexToBubbleDown = leftChildIndex 102 | continue 103 | } 104 | 105 | if (rightChildPriority < leftChildPriority) { 106 | this.#swap(rightChildIndex, indexToBubbleDown) 107 | indexToBubbleDown = rightChildIndex 108 | continue 109 | } 110 | } 111 | 112 | // Here we now both can't be less than the current node 113 | // aka we know only one of the cases below will be true 114 | 115 | if (leftChildPriority < currentNodePriority) { 116 | this.#swap(leftChildIndex, indexToBubbleDown) 117 | indexToBubbleDown = leftChildIndex 118 | continue 119 | } 120 | 121 | if (rightChildPriority < currentNodePriority) { 122 | this.#swap(rightChildIndex, indexToBubbleDown) 123 | indexToBubbleDown = rightChildIndex 124 | continue 125 | } 126 | 127 | break 128 | } 129 | } 130 | 131 | size() { 132 | return this.heap.length 133 | } 134 | 135 | dequeue() { 136 | if (this.isEmpty()) { 137 | return null 138 | } 139 | 140 | if (this.heap.length === 1) { 141 | const lastNode = this.heap.pop() 142 | return lastNode.value 143 | } 144 | 145 | const lastItem = this.heap.pop() 146 | const firstItem = this.heap[0] 147 | 148 | this.heap[0] = lastItem 149 | this.#bubbleDown() 150 | 151 | return firstItem.value 152 | } 153 | 154 | #getIndexOfLastNonLeafNode() { 155 | const indexOfLastItem = this.heap.length - 1 156 | return this.#getParentIndex(indexOfLastItem) 157 | } 158 | 159 | heapify(array) { 160 | this.heap = array 161 | 162 | let indexOfLastNonLeafNode = this.#getIndexOfLastNonLeafNode() 163 | 164 | while (indexOfLastNonLeafNode >= 0) { 165 | this.#bubbleDown(indexOfLastNonLeafNode) 166 | indexOfLastNonLeafNode-- 167 | } 168 | } 169 | 170 | updatePriority(value, priority) { 171 | const nodeIndex = this.heap.findIndex((node) => node.value === value) 172 | if (nodeIndex === -1) { 173 | return null 174 | } 175 | 176 | const hasPriorityIncreased = priority <= this.heap[nodeIndex].priority 177 | const hasPriorityDecreased = priority >= this.heap[nodeIndex].priority 178 | 179 | if (hasPriorityIncreased) { 180 | this.heap[nodeIndex].priority = priority 181 | this.#bubbleUp(nodeIndex) 182 | return 183 | } 184 | 185 | if (hasPriorityDecreased) { 186 | this.heap[nodeIndex].priority = priority 187 | this.#bubbleDown(nodeIndex) 188 | return 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/queues/MinPriorityQueue.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { MinPriorityQueue } from './MinPriorityQueue' 3 | 4 | // Test for creating an empty priority queue 5 | it('should create an empty priority queue', () => { 6 | const pq = new MinPriorityQueue() 7 | expect(pq.isEmpty()).toBe(true) 8 | }) 9 | 10 | // Test for enqueue operation 11 | it('should enqueue elements with their priorities', () => { 12 | const pq = new MinPriorityQueue() 13 | pq.enqueue('NodeA', 3) // value 'NodeA' with priority 3 14 | pq.enqueue('NodeB', 1) // value 'NodeB' with priority 1 15 | pq.enqueue('NodeC', 2) // value 'NodeC' with priority 2 16 | 17 | expect(pq.peek()).toBe('NodeB') // The element with the highest priority 18 | }) 19 | 20 | // Test for dequeue operation 21 | it('should dequeue elements in priority order', () => { 22 | const pq = new MinPriorityQueue() 23 | pq.enqueue('NodeA', 3) 24 | pq.enqueue('NodeB', 1) 25 | pq.enqueue('NodeC', 2) 26 | 27 | expect(pq.dequeue()).toBe('NodeB') 28 | expect(pq.dequeue()).toBe('NodeC') 29 | expect(pq.dequeue()).toBe('NodeA') 30 | expect(pq.isEmpty()).toBe(true) 31 | }) 32 | 33 | // // Test for peek operation 34 | it('should peek at the highest priority element without removing it', () => { 35 | const pq = new MinPriorityQueue() 36 | pq.enqueue('NodeA', 2) 37 | pq.enqueue('NodeB', 1) 38 | 39 | expect(pq.peek()).toBe('NodeB') 40 | expect(pq.isEmpty()).toBe(false) 41 | }) 42 | 43 | // // Test for priority queue size 44 | it('should return the correct size of the priority queue', () => { 45 | const pq = new MinPriorityQueue() 46 | pq.enqueue('NodeA', 1) 47 | pq.enqueue('NodeB', 2) 48 | pq.enqueue('NodeC', 3) 49 | 50 | expect(pq.size()).toBe(3) 51 | }) 52 | 53 | // // Test for empty queue behavior 54 | it('should handle operations on an empty queue correctly', () => { 55 | const pq = new MinPriorityQueue() 56 | 57 | expect(pq.dequeue()).toBeNull() 58 | expect(pq.peek()).toBeNull() 59 | }) 60 | 61 | // // Test for handling different data types 62 | it('should handle different data types as values', () => { 63 | const pq = new MinPriorityQueue() 64 | pq.enqueue(10, 2) 65 | pq.enqueue('Test', 3) 66 | pq.enqueue({ id: 1, name: 'Object' }, 1) 67 | 68 | expect(pq.dequeue()).toEqual({ id: 1, name: 'Object' }) 69 | expect(pq.dequeue()).toBe(10) 70 | expect(pq.dequeue()).toBe('Test') 71 | }) 72 | 73 | // // Test for handling elements with the same priority 74 | it('should maintain order for elements with the same priority', () => { 75 | const pq = new MinPriorityQueue() 76 | pq.enqueue('NodeA', 1) 77 | pq.enqueue('NodeB', 1) 78 | 79 | expect(pq.dequeue()).toBe('NodeA') // As 'NodeA' was enqueued first 80 | expect(pq.dequeue()).toBe('NodeB') 81 | }) 82 | 83 | // Test for updatePriority operation 84 | it('should update the priority of an existing element', () => { 85 | const pq = new MinPriorityQueue() 86 | pq.enqueue('NodeA', 3) 87 | pq.enqueue('NodeB', 1) 88 | pq.enqueue('NodeC', 2) 89 | 90 | pq.updatePriority('NodeA', 0) // Update 'NodeA' priority to 0, making it the highest priority 91 | expect(pq.peek()).toBe('NodeA') 92 | }) 93 | 94 | // Test for updatePriority operation on non-existing element 95 | it('should not update priority if the element does not exist', () => { 96 | const pq = new MinPriorityQueue() 97 | pq.enqueue('NodeA', 3) 98 | pq.enqueue('NodeB', 1) 99 | 100 | pq.updatePriority('NodeC', 0) // 'NodeC' does not exist 101 | expect(pq.peek()).toBe('NodeB') // Priority queue remains unchanged 102 | }) 103 | 104 | // Test for updatePriority with the same priority 105 | it('should not change the queue if the updated priority is the same', () => { 106 | const pq = new MinPriorityQueue() 107 | pq.enqueue('NodeA', 1) 108 | pq.enqueue('NodeB', 2) 109 | 110 | pq.updatePriority('NodeA', 1) // Same priority for 'NodeA' 111 | expect(pq.peek()).toBe('NodeA') 112 | }) 113 | 114 | // Test for updatePriority causing a reordering in the queue 115 | it('should reorder the elements correctly when a priority is updated', () => { 116 | const pq = new MinPriorityQueue() 117 | pq.enqueue('NodeA', 3) 118 | pq.enqueue('NodeB', 1) 119 | pq.enqueue('NodeC', 2) 120 | 121 | pq.updatePriority('NodeA', 0) // 'NodeA' becomes highest priority 122 | expect(pq.dequeue()).toBe('NodeA') 123 | expect(pq.dequeue()).toBe('NodeB') 124 | expect(pq.dequeue()).toBe('NodeC') 125 | }) 126 | 127 | // Test for updatePriority in a queue with multiple elements having the same priority 128 | it('should handle updating priorities in a queue with duplicate priorities correctly', () => { 129 | const pq = new MinPriorityQueue() 130 | pq.enqueue('NodeA', 1) 131 | pq.enqueue('NodeB', 1) 132 | pq.enqueue('NodeC', 2) 133 | 134 | pq.updatePriority('NodeB', 0) // 'NodeB' becomes highest priority 135 | expect(pq.dequeue()).toBe('NodeB') 136 | expect(pq.dequeue()).toBe('NodeA') 137 | expect(pq.dequeue()).toBe('NodeC') 138 | }) 139 | -------------------------------------------------------------------------------- /src/queues/README.md: -------------------------------------------------------------------------------- 1 | # Queues 2 | 3 | Queues are a data structure that follow the FIFO (First In First Out) principle. This means that the first element added to the queue will be the first element removed from the queue. It is like a line at a grocery store, the first person in line will be the first person to be served. 4 | 5 | It has the operations: enqueue, dequeue, peek, size and isEmpty. 6 | 7 | This implementation uses JS arrays, but it is more efficient to use a linked lists, which is the typical implementation of a queue. 8 | -------------------------------------------------------------------------------- /src/queues/RegularQueue.js: -------------------------------------------------------------------------------- 1 | // We're using arrays here for convenience, but this is not the best data structure for this purpose. 2 | // Linked Lists are better for Queues. 3 | export class Queue { 4 | constructor() { 5 | this.queue = [] 6 | } 7 | 8 | enqueue(value) { 9 | this.queue.push(value) 10 | } 11 | 12 | dequeue() { 13 | return this.queue.shift() 14 | } 15 | 16 | peek() { 17 | return this.queue[0] 18 | } 19 | 20 | size() { 21 | return this.queue.length 22 | } 23 | 24 | isEmpty() { 25 | return this.size() === 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/queues/RegularQueue.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { Queue } from './RegularQueue' 3 | 4 | it('should enqueue, peek, dequeue, size, isEmpty', () => { 5 | const queue = new Queue() 6 | 7 | queue.enqueue(1) 8 | queue.enqueue(2) 9 | queue.enqueue(3) 10 | 11 | expect(queue.peek()).toBe(1) 12 | expect(queue.dequeue()).toBe(1) 13 | expect(queue.dequeue()).toBe(2) 14 | expect(queue.dequeue()).toBe(3) 15 | expect(queue.dequeue()).toBeUndefined() 16 | 17 | expect(queue.size()).toBe(0) 18 | expect(queue.isEmpty()).toBe(true) 19 | }) 20 | -------------------------------------------------------------------------------- /src/recursion/countOccurrences.js: -------------------------------------------------------------------------------- 1 | export const countOccurrences = ( 2 | arrOfNums, 3 | num, 4 | totalCount = 0, 5 | currentIndex = 0 6 | ) => { 7 | if (currentIndex === arrOfNums.length) { 8 | return totalCount 9 | } 10 | 11 | if (arrOfNums[currentIndex] === num) { 12 | totalCount++ 13 | } 14 | 15 | return countOccurrences(arrOfNums, num, totalCount, currentIndex + 1) 16 | } 17 | -------------------------------------------------------------------------------- /src/recursion/countOccurrences.test.js: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | import { countOccurrences } from './countOccurrences' 3 | 4 | it('count occurrences', () => { 5 | expect(countOccurrences([4, 2, 7, 4, 4, 1, 2], 4)).toBe(3) 6 | }) 7 | -------------------------------------------------------------------------------- /src/recursion/factorial.js: -------------------------------------------------------------------------------- 1 | export function factorial(num) { 2 | if (num === 1) return 1 3 | 4 | return num * factorial(num - 1) 5 | } 6 | -------------------------------------------------------------------------------- /src/recursion/factorial.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { factorial } from './factorial' 3 | 4 | it('should return 120 for 5', () => { 5 | expect(factorial(5)).toBe(120) 6 | }) 7 | -------------------------------------------------------------------------------- /src/recursion/fibonacci.js: -------------------------------------------------------------------------------- 1 | // Fibonacci sequence 2 | // It is a sequence of numbers where each number is the sum of the two preceding ones, starting from 0 and 1. 3 | // For example: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 4 | // Fibonacci of 10 step by step: 5 | // 1. fibonacci(10) = fibonacci(9) + fibonacci(8) 6 | // 2. fibonacci(9) = fibonacci(8) + fibonacci(7) 7 | // 3. fibonacci(8) = fibonacci(7) + fibonacci(6) 8 | // 4. fibonacci(7) = fibonacci(6) + fibonacci(5) 9 | // 5. fibonacci(6) = fibonacci(5) + fibonacci(4) 10 | // 6. fibonacci(5) = fibonacci(4) + fibonacci(3) 11 | // 7. fibonacci(4) = fibonacci(3) + fibonacci(2) 12 | // 8. fibonacci(3) = fibonacci(2) + fibonacci(1) 13 | // 9. fibonacci(2) = fibonacci(1) + fibonacci(0) 14 | // 10. fibonacci(1) = 1 15 | // From here, we go up again: 16 | // 11. fibonacci(2) = 1 + 0 = 1 17 | // 12. fibonacci(3) = 1 + 1 = 2 18 | // 13. fibonacci(4) = 2 + 1 = 3 19 | export function fibonacci(n) { 20 | // Base case: if n is less than 2, it can only be 0 or 1 21 | // In the Fibonacci sequence, the first two numbers are 0 and 1 22 | // So, we return n itself 23 | if (n < 2) { 24 | return n 25 | } 26 | 27 | // Recursive case: if n is not less than 2, we calculate the Fibonacci number 28 | // by adding the two preceding Fibonacci numbers 29 | // This is done by calling the fibonacci function recursively with n - 1 and n - 2 30 | return fibonacci(n - 1) + fibonacci(n - 2) 31 | } 32 | -------------------------------------------------------------------------------- /src/recursion/hasAdjacentDuplicates.js: -------------------------------------------------------------------------------- 1 | export const hasAdjacentDuplicates = (str, index = 0) => { 2 | const isOutOfBonds = index + 1 === str.length 3 | 4 | if (isOutOfBonds) { 5 | return false 6 | } 7 | 8 | const letter = str[index] 9 | const adjacentLetter = str[index + 1] 10 | 11 | if (letter === adjacentLetter) { 12 | return true 13 | } 14 | 15 | return hasAdjacentDuplicates(str, index + 1) 16 | } 17 | -------------------------------------------------------------------------------- /src/recursion/hasAdjacentDuplicates.test.js: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | import { hasAdjacentDuplicates } from './hasAdjacentDuplicates' 3 | 4 | it('has adjacent duplicates', () => { 5 | expect(hasAdjacentDuplicates('hello')).toBe(true) 6 | expect(hasAdjacentDuplicates('world')).toBe(false) 7 | }) 8 | -------------------------------------------------------------------------------- /src/recursion/reverseString.js: -------------------------------------------------------------------------------- 1 | export const reverseString = (str, index = 0, accumulator = '') => { 2 | if (index === str.length) { 3 | return accumulator 4 | } 5 | 6 | accumulator = str[index] + accumulator 7 | 8 | return reverseString(str, index + 1, accumulator) 9 | } 10 | -------------------------------------------------------------------------------- /src/recursion/reverseString.test.js: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | import { reverseString } from './reverseString' 3 | 4 | it('reverse string', () => { 5 | expect(reverseString('hello')).toBe('olleh') 6 | expect(reverseString('world')).toBe('dlrow') 7 | }) 8 | -------------------------------------------------------------------------------- /src/recursion/sumOfDigits.js: -------------------------------------------------------------------------------- 1 | export const sumOfDigits = (num) => { 2 | const isSingleNumber = num <= 9 3 | if (isSingleNumber) { 4 | return num 5 | } 6 | 7 | const lastNumber = num % 10 8 | const newNumber = Math.floor(num / 10) 9 | 10 | return lastNumber + sumOfDigits(newNumber) 11 | } 12 | -------------------------------------------------------------------------------- /src/recursion/sumOfDigits.test.js: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | import { sumOfDigits } from './sumOfDigits' 3 | 4 | it('sumOfDigits', () => { 5 | expect(sumOfDigits(123)).toEqual(6) 6 | expect(sumOfDigits(1234)).toEqual(10) 7 | expect(sumOfDigits(12345)).toEqual(15) 8 | expect(sumOfDigits(123456)).toEqual(21) 9 | expect(sumOfDigits(1234567)).toEqual(28) 10 | expect(sumOfDigits(12345678)).toEqual(36) 11 | expect(sumOfDigits(123456789)).toEqual(45) 12 | expect(sumOfDigits(1234567890)).toEqual(45) 13 | }) 14 | -------------------------------------------------------------------------------- /src/recursion/ways-to-climb-stairs.js: -------------------------------------------------------------------------------- 1 | export function waysToClimbStairs(target, possibleSteps) { 2 | if (target === 0) return 1 3 | 4 | let nbWays = 0 5 | 6 | possibleSteps.forEach((step) => { 7 | if (target - step >= 0) { 8 | nbWays += waysToClimbStairs(target - step, possibleSteps) 9 | } 10 | }) 11 | 12 | return nbWays 13 | } 14 | 15 | export function waysToClimbStairsWithMemoization( 16 | target, 17 | possibleSteps, 18 | memoization = {} 19 | ) { 20 | if (target === 0) return 1 21 | 22 | // If we already computed the number of ways to climb the stairs for the target, we return it 23 | // There is no need to recompute it 24 | if (memoization[target]) return memoization[target] 25 | 26 | let nbWays = 0 27 | 28 | // Once this process is done, we store the result in the memoization object 29 | possibleSteps.forEach((step) => { 30 | if (target - step >= 0) { 31 | nbWays += waysToClimbStairsWithMemoization( 32 | target - step, 33 | possibleSteps, 34 | memoization 35 | ) 36 | } 37 | }) 38 | 39 | memoization[target] = nbWays 40 | 41 | return nbWays 42 | } 43 | -------------------------------------------------------------------------------- /src/recursion/ways-to-climb-stairs.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { waysToClimbStairs } from './ways-to-climb-stairs' 3 | 4 | it('should return 11 for [2, 4, 5, 8] and 10 as target', () => { 5 | expect(waysToClimbStairs(10, [2, 4, 5, 8])).toBe(11) 6 | }) 7 | -------------------------------------------------------------------------------- /src/stacks/README.md: -------------------------------------------------------------------------------- 1 | # Stacks 2 | 3 | Stack is a linear data structure which follows a particular order in which the operations are performed. The is usually LIFO(Last In First Out). 4 | 5 | You have add, remove and peek operations. 6 | 7 | It is like a stack of books. You can only add a book to the top of the stack and you can only remove the top book from the stack. 8 | -------------------------------------------------------------------------------- /src/stacks/index.js: -------------------------------------------------------------------------------- 1 | export class Stack { 2 | constructor() { 3 | this.stack = [] 4 | } 5 | 6 | push(item) { 7 | this.stack.push(item) 8 | } 9 | 10 | pop() { 11 | return this.stack.pop() 12 | } 13 | 14 | peek() { 15 | return this.stack[this.stack.length - 1] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/stacks/index.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { Stack } from './index' 3 | 4 | it('should push, pop, and peek', () => { 5 | const stack = new Stack() 6 | stack.push(1) 7 | stack.push(2) 8 | stack.push(3) 9 | expect(stack.pop()).toBe(3) 10 | expect(stack.pop()).toBe(2) 11 | expect(stack.pop()).toBe(1) 12 | expect(stack.pop()).toBe(undefined) 13 | stack.push(1) 14 | stack.push(2) 15 | stack.push(3) 16 | expect(stack.peek()).toBe(3) 17 | expect(stack.peek()).toBe(3) 18 | expect(stack.pop()).toBe(3) 19 | expect(stack.peek()).toBe(2) 20 | expect(stack.pop()).toBe(2) 21 | expect(stack.peek()).toBe(1) 22 | expect(stack.pop()).toBe(1) 23 | expect(stack.peek()).toBe(undefined) 24 | expect(stack.pop()).toBe(undefined) 25 | }) 26 | -------------------------------------------------------------------------------- /src/trees/avl-tree.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.left = null 5 | this.right = null 6 | this.height = 1 7 | } 8 | } 9 | 10 | export class AVLTree { 11 | constructor() { 12 | this.root = null 13 | } 14 | 15 | isEmpty() { 16 | return this.root === null 17 | } 18 | 19 | getHeight(node) { 20 | if (!node) { 21 | return 0 22 | } 23 | 24 | return node.height 25 | } 26 | 27 | getBalanceFactor(node) { 28 | if (!node) { 29 | return 0 30 | } 31 | 32 | return this.getHeight(node.left) - this.getHeight(node.right) 33 | } 34 | 35 | getNewHeight(node) { 36 | if (!node) { 37 | return 0 38 | } 39 | 40 | return Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1 41 | } 42 | 43 | rightRotate(node) { 44 | let newRoot = node.left 45 | let newLeftChildOfOldRoot = newRoot.right 46 | 47 | let oldRoot = node 48 | 49 | node = newRoot 50 | 51 | node.right = oldRoot 52 | oldRoot.left = newLeftChildOfOldRoot 53 | 54 | oldRoot.height = this.getNewHeight(oldRoot) 55 | node.height = this.getNewHeight(node) 56 | 57 | console.log({ 58 | oldRoot, 59 | node, 60 | }) 61 | 62 | return node 63 | } 64 | 65 | leftRotate(node) { 66 | let newRoot = node.right 67 | let oldRoot = node 68 | let newRightChildOfOldRoot = newRoot.left 69 | 70 | node = newRoot 71 | newRoot.left = oldRoot 72 | oldRoot.right = newRightChildOfOldRoot 73 | 74 | oldRoot.height = this.getNewHeight(oldRoot) 75 | node.height = this.getNewHeight(node) 76 | 77 | return node 78 | } 79 | 80 | balanceAndRotate(currentNode) { 81 | currentNode.height = this.getNewHeight(currentNode) 82 | 83 | const balanceFactor = this.getBalanceFactor(currentNode) 84 | 85 | if (balanceFactor > 1) { 86 | // If balance factor is less then 0 for left child, then it is a LR imbalance 87 | // It means the current node is skewed towards the left and the left child is skewed towards the right 88 | // It becomes like a zig-zag shape 89 | // If that is the case, before doing the right rotation, 90 | // since the current node is skewed towards the left 91 | // we need to do a left rotation on the left child 92 | // Because the left child is skewed towards the right 93 | if (this.getBalanceFactor(currentNode.left) < 0) { 94 | currentNode.left = this.leftRotate(currentNode.left) 95 | } 96 | return this.rightRotate(currentNode) 97 | } else if (balanceFactor < -1) { 98 | // Same logic as above, but for the right child 99 | // If the currentNode is imbalanced towards the right 100 | // and the right child is imbalanced towards the left 101 | // then it is a RL imbalance 102 | // It goes down right, then left, imbalanced 103 | // We straighten it out by doing a right rotation on the right child 104 | // Now it is a RR imbalance and we just do a left rotation on the currentNode 105 | if (this.getBalanceFactor(currentNode.right) > 0) { 106 | currentNode.right = this.rightRotate(currentNode.right) 107 | } 108 | return this.leftRotate(currentNode) 109 | } 110 | 111 | return currentNode 112 | } 113 | 114 | insert(value, currentNode = this.root) { 115 | if (!currentNode) { 116 | const newNode = new Node(value) 117 | if (!this.root) { 118 | this.root = newNode 119 | } 120 | return newNode 121 | } 122 | 123 | if (value > currentNode.value) { 124 | currentNode.right = this.insert(value, currentNode.right) 125 | } else { 126 | currentNode.left = this.insert(value, currentNode.left) 127 | } 128 | 129 | return this.balanceAndRotate(currentNode) 130 | } 131 | 132 | find(value, currentNode = this.root) { 133 | if (!currentNode) { 134 | return null 135 | } 136 | 137 | if (currentNode.value === value) { 138 | return currentNode 139 | } 140 | 141 | if (value > currentNode.value) { 142 | return this.find(value, currentNode.right) 143 | } else { 144 | return this.find(value, currentNode.left) 145 | } 146 | } 147 | 148 | delete(value, currentNode = this.root) { 149 | if (!currentNode) { 150 | return currentNode 151 | } 152 | 153 | if (value > currentNode.value) { 154 | currentNode.right = this.delete(value, currentNode.right) 155 | } else if (value < currentNode.value) { 156 | currentNode.left = this.delete(value, currentNode.left) 157 | } else { 158 | if (!currentNode.left) { 159 | return currentNode.right 160 | } else if (!currentNode.right) { 161 | return currentNode.left 162 | } 163 | 164 | let minFromRightSubTree = currentNode.right 165 | 166 | while (minFromRightSubTree.left) { 167 | minFromRightSubTree = minFromRightSubTree.left 168 | } 169 | 170 | currentNode.value = minFromRightSubTree.value 171 | currentNode.right = this.delete( 172 | minFromRightSubTree.value, 173 | currentNode.right 174 | ) 175 | } 176 | 177 | return this.balanceAndRotate(currentNode) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/trees/avl-tree.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { AVLTree } from './avl-tree' 3 | 4 | // Test for creating an AVL tree 5 | it('should create an empty AVL tree', () => { 6 | const avl = new AVLTree() 7 | expect(avl.isEmpty()).toBe(true) 8 | }) 9 | 10 | // Test for inserting elements 11 | it('should insert elements and maintain balance', () => { 12 | const avl = new AVLTree() 13 | avl.insert(3) 14 | avl.insert(2) 15 | const newRoot = avl.insert(1) 16 | 17 | // Check balance after insertions 18 | expect(newRoot.value).toBe(2) // Root should be 2 after rotations 19 | expect(newRoot.left.value).toBe(1) 20 | expect(newRoot.right.value).toBe(3) 21 | }) 22 | 23 | // Test for Right Rotation (LL Imbalance) 24 | it('should perform right rotation for LL imbalance', () => { 25 | const avl = new AVLTree() 26 | avl.insert(3) 27 | avl.insert(2) 28 | const newRoot = avl.insert(1) 29 | 30 | expect(newRoot.value).toBe(2) // Right rotation should make 2 the root 31 | expect(newRoot.left.value).toBe(1) 32 | expect(newRoot.right.value).toBe(3) 33 | }) 34 | 35 | // Test for Left Rotation (RR Imbalance) 36 | it('should perform left rotation for RR imbalance', () => { 37 | const avl = new AVLTree() 38 | avl.insert(1) 39 | avl.insert(2) 40 | const newRoot = avl.insert(3) 41 | 42 | expect(newRoot.value).toBe(2) // Left rotation should make 2 the root 43 | expect(newRoot.left.value).toBe(1) 44 | expect(newRoot.right.value).toBe(3) 45 | }) 46 | 47 | // Test for Left-Right Rotation (LR Imbalance) 48 | it('should perform left-right rotation for LR imbalance', () => { 49 | const avl = new AVLTree() 50 | avl.insert(3) 51 | avl.insert(1) 52 | const newRoot = avl.insert(2) 53 | 54 | expect(newRoot.value).toBe(2) // Left-right rotation should make 2 the root 55 | expect(newRoot.left.value).toBe(1) 56 | expect(newRoot.right.value).toBe(3) 57 | }) 58 | 59 | // Test for Right-Left Rotation (RL Imbalance) 60 | it('should perform right-left rotation for RL imbalance', () => { 61 | const avl = new AVLTree() 62 | avl.insert(1) 63 | avl.insert(3) 64 | const newRoot = avl.insert(2) 65 | 66 | expect(newRoot.value).toBe(2) // Right-left rotation should make 2 the root 67 | expect(newRoot.left.value).toBe(1) 68 | expect(newRoot.right.value).toBe(3) 69 | }) 70 | 71 | it('should delete a leaf node correctly', () => { 72 | const avl = new AVLTree() 73 | avl.insert(10) 74 | avl.insert(5) 75 | avl.insert(15) 76 | avl.delete(15) 77 | 78 | expect(avl.find(15)).toBeNull() 79 | expect(avl.find(10).right).toBeNull() 80 | }) 81 | -------------------------------------------------------------------------------- /src/trees/binary-search-tree.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.left = null 5 | this.right = null 6 | } 7 | } 8 | 9 | export class BinarySearchTree { 10 | constructor() { 11 | this.root = null 12 | } 13 | 14 | isEmpty() { 15 | return this.root === null 16 | } 17 | 18 | insert(value, currentNode = this.root) { 19 | if (!currentNode) { 20 | const newNode = new Node(value) 21 | if (!this.root) { 22 | this.root = newNode 23 | } 24 | return newNode 25 | } 26 | 27 | if (value > currentNode.value) { 28 | currentNode.right = this.insert(value, currentNode.right) 29 | } else { 30 | currentNode.left = this.insert(value, currentNode.left) 31 | } 32 | 33 | return currentNode 34 | } 35 | 36 | find(value, currentNode = this.root) { 37 | if (!currentNode) { 38 | return null 39 | } 40 | 41 | if (currentNode.value === value) { 42 | return currentNode 43 | } 44 | 45 | if (value > currentNode.value) { 46 | return this.find(value, currentNode.right) 47 | } else { 48 | return this.find(value, currentNode.left) 49 | } 50 | } 51 | 52 | delete(value, currentNode = this.root) { 53 | if (!currentNode) { 54 | return currentNode 55 | } 56 | 57 | if (value > currentNode.value) { 58 | currentNode.right = this.delete(value, currentNode.right) 59 | } else if (value < currentNode.value) { 60 | currentNode.left = this.delete(value, currentNode.left) 61 | } else { 62 | if (!currentNode.left) { 63 | return currentNode.right 64 | } else if (!currentNode.right) { 65 | return currentNode.left 66 | } 67 | 68 | let minFromRightSubTree = currentNode.right 69 | 70 | while (minFromRightSubTree.left) { 71 | minFromRightSubTree = minFromRightSubTree.left 72 | } 73 | 74 | currentNode.value = minFromRightSubTree.value 75 | currentNode.right = this.delete( 76 | minFromRightSubTree.value, 77 | currentNode.right 78 | ) 79 | } 80 | 81 | return currentNode 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/trees/binary-search-tree.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { BinarySearchTree } from './binary-search-tree' 3 | 4 | // Test for creating a new binary search tree 5 | it('should create an empty binary search tree', () => { 6 | const bst = new BinarySearchTree() 7 | expect(bst).toBeDefined() 8 | expect(bst.isEmpty()).toBe(true) 9 | }) 10 | 11 | // Test for inserting elements 12 | it('should insert elements and maintain BST properties', () => { 13 | const bst = new BinarySearchTree() 14 | bst.insert(10) 15 | bst.insert(5) 16 | bst.insert(15) 17 | bst.insert(3) 18 | bst.insert(7) 19 | 20 | expect(bst.find(5).left.value).toBe(3) 21 | expect(bst.find(5).right.value).toBe(7) 22 | expect(bst.find(10).right.value).toBe(15) 23 | }) 24 | 25 | // Test for finding elements 26 | it('should find elements in the BST', () => { 27 | const bst = new BinarySearchTree() 28 | bst.insert(10) 29 | bst.insert(5) 30 | bst.insert(15) 31 | 32 | expect(bst.find(10).value).toBe(10) 33 | expect(bst.find(5).value).toBe(5) 34 | expect(bst.find(15).value).toBe(15) 35 | expect(bst.find(20)).toBeNull() 36 | }) 37 | 38 | // Test for deleting a leaf node (no children) 39 | it('should delete a leaf node correctly', () => { 40 | const bst = new BinarySearchTree() 41 | bst.insert(10) 42 | bst.insert(5) 43 | bst.insert(15) 44 | bst.delete(15) 45 | 46 | expect(bst.find(15)).toBeNull() 47 | expect(bst.find(10).right).toBeNull() 48 | }) 49 | 50 | // Test for deleting a node with only a left child 51 | it('should delete a node with only a left child correctly', () => { 52 | const bst = new BinarySearchTree() 53 | bst.insert(10) 54 | bst.insert(5) 55 | bst.insert(3) 56 | bst.delete(5) 57 | 58 | expect(bst.find(5)).toBeNull() 59 | expect(bst.find(10).left.value).toBe(3) 60 | }) 61 | 62 | // Test for deleting a node with only a right child 63 | it('should delete a node with only a right child correctly', () => { 64 | const bst = new BinarySearchTree() 65 | bst.insert(10) 66 | bst.insert(15) 67 | bst.insert(20) 68 | bst.delete(15) 69 | 70 | expect(bst.find(15)).toBeNull() 71 | expect(bst.find(10).right.value).toBe(20) 72 | }) 73 | 74 | // Test for deleting a node with two children 75 | it('should delete a node with two children correctly', () => { 76 | const bst = new BinarySearchTree() 77 | bst.insert(10) 78 | bst.insert(5) 79 | bst.insert(15) 80 | bst.insert(3) 81 | bst.insert(7) 82 | bst.insert(12) 83 | bst.insert(17) 84 | bst.delete(15) 85 | 86 | // Check the successor of 15 (in this case, 17) has replaced 15 87 | expect(bst.find(15)).toBeNull() 88 | expect(bst.find(10).right.value).not.toBe(15) 89 | 90 | // Further checks can be added to ensure the structure of the tree 91 | }) 92 | -------------------------------------------------------------------------------- /src/trees/binary-tree.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(value) { 3 | this.value = value 4 | this.left = null 5 | this.right = null 6 | } 7 | } 8 | 9 | export class BinaryTree { 10 | constructor() { 11 | this.root = null 12 | } 13 | 14 | isEmpty() { 15 | return this.root === null 16 | } 17 | 18 | insert(value) { 19 | const newNode = new Node(value) 20 | 21 | if (this.isEmpty()) { 22 | this.root = newNode 23 | return 24 | } 25 | 26 | const queue = [this.root] 27 | 28 | while (queue.length) { 29 | const currentNode = queue.pop() 30 | 31 | if (currentNode.right && currentNode.left) { 32 | queue.push(currentNode.right) 33 | queue.push(currentNode.left) 34 | continue 35 | } 36 | 37 | if (!currentNode.left) { 38 | currentNode.left = newNode 39 | break 40 | } 41 | 42 | if (!currentNode.right) { 43 | currentNode.right = newNode 44 | break 45 | } 46 | } 47 | } 48 | 49 | preorderTraversal(currentNode = this.root, result = []) { 50 | if (!currentNode) return result 51 | 52 | result.push(currentNode.value) 53 | this.preorderTraversal(currentNode.left, result) 54 | this.preorderTraversal(currentNode.right, result) 55 | 56 | return result 57 | } 58 | 59 | inorderTraversal(currentNode = this.root, result = []) { 60 | if (!currentNode) return result 61 | 62 | this.inorderTraversal(currentNode.left, result) 63 | result.push(currentNode.value) 64 | this.inorderTraversal(currentNode.right, result) 65 | 66 | return result 67 | } 68 | 69 | postorderTraversal(currentNode = this.root, result = []) { 70 | if (!currentNode) return result 71 | 72 | console.log(currentNode) 73 | 74 | this.postorderTraversal(currentNode.left, result) 75 | this.inorderTraversal(currentNode.right, result) 76 | result.push(currentNode.value) 77 | 78 | return result 79 | } 80 | 81 | toArray() { 82 | if (this.isEmpty()) { 83 | return null 84 | } 85 | 86 | let result = [this.root.value] 87 | 88 | const queue = [this.root] 89 | 90 | while (queue.length) { 91 | const currentNode = queue.pop() 92 | 93 | if (currentNode.right && currentNode.left) { 94 | queue.push(currentNode.right) 95 | queue.push(currentNode.left) 96 | 97 | result = [...result, currentNode.left.value, currentNode.right.value] 98 | continue 99 | } 100 | 101 | if (currentNode.left) { 102 | queue.push(currentNode.left) 103 | result = [...result, currentNode.left.value] 104 | } 105 | 106 | if (currentNode.right) { 107 | queue.push(currentNode.right) 108 | result = [...result, currentNode.right.value] 109 | } 110 | } 111 | 112 | console.log(result) 113 | 114 | return result 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/trees/binary-tree.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { BinaryTree } from './binary-tree' 3 | 4 | // Test for creating a new binary tree 5 | it('should create an empty binary tree', () => { 6 | const tree = new BinaryTree() 7 | expect(tree).toBeDefined() 8 | expect(tree.isEmpty()).toBe(true) 9 | }) 10 | 11 | // Test for inserting elements 12 | it('should insert elements', () => { 13 | const tree = new BinaryTree() 14 | tree.insert(3) 15 | tree.insert(1) 16 | tree.insert(4) 17 | 18 | expect(tree.toArray()).toEqual([3, 1, 4]) // Simple array representation 19 | }) 20 | 21 | // Test for inorder traversal 22 | it('should perform inorder traversal', () => { 23 | const tree = new BinaryTree() 24 | 25 | tree.insert(3) 26 | tree.insert(1) 27 | tree.insert(4) 28 | tree.insert(2) 29 | tree.insert(5) 30 | tree.insert(6) 31 | 32 | expect(tree.inorderTraversal()).toEqual([6, 2, 1, 5, 3, 4]) 33 | }) 34 | 35 | // Test for preorder traversal 36 | it('should perform preorder traversal', () => { 37 | const tree = new BinaryTree() 38 | tree.insert(3) 39 | tree.insert(1) 40 | tree.insert(4) 41 | 42 | expect(tree.preorderTraversal()).toEqual([3, 1, 4]) 43 | }) 44 | 45 | // Test for postorder traversal 46 | it('should perform postorder traversal', () => { 47 | const tree = new BinaryTree() 48 | tree.insert(3) 49 | tree.insert(1) 50 | tree.insert(4) 51 | 52 | expect(tree.postorderTraversal()).toEqual([1, 4, 3]) 53 | }) 54 | -------------------------------------------------------------------------------- /src/trie/index.js: -------------------------------------------------------------------------------- 1 | class TrieNode { 2 | constructor() { 3 | this.children = {} 4 | this.isEndOfWord = false 5 | } 6 | } 7 | 8 | export class Trie { 9 | constructor() { 10 | this.root = new TrieNode() 11 | } 12 | 13 | search(word) { 14 | let currentNode = this.root 15 | 16 | for (let i = 0; i < word.length; i++) { 17 | let char = word[i] 18 | 19 | if (!currentNode.children[char]) { 20 | return false 21 | } 22 | 23 | currentNode = currentNode.children[char] 24 | } 25 | 26 | return currentNode.isEndOfWord 27 | } 28 | 29 | startsWith(prefix) { 30 | let currentNode = this.root 31 | 32 | for (let i = 0; i < prefix.length; i++) { 33 | let char = prefix[i] 34 | 35 | if (!currentNode.children[char]) { 36 | return false 37 | } 38 | 39 | currentNode = currentNode.children[char] 40 | } 41 | 42 | return true 43 | } 44 | 45 | insert(word) { 46 | let currentNode = this.root 47 | 48 | for (let i = 0; i < word.length; i++) { 49 | let char = word[i] 50 | 51 | if (!currentNode.children[char]) { 52 | currentNode.children[char] = new TrieNode() 53 | } 54 | 55 | currentNode = currentNode.children[char] 56 | } 57 | 58 | currentNode.isEndOfWord = true 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/trie/index.test.js: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest' 2 | import { Trie } from '.' 3 | 4 | it('should insert and search words', () => { 5 | const trie = new Trie() 6 | 7 | trie.insert('apple') 8 | expect(trie.search('apple')).toBe(true) 9 | expect(trie.search('app')).toBe(false) 10 | expect(trie.startsWith('app')).toBe(true) 11 | trie.insert('app') 12 | expect(trie.search('app')).toBe(true) 13 | }) 14 | --------------------------------------------------------------------------------