npm test) and check your work against// Returns simply true/false for presence
function binarySearch(array, target) {
if (array.length === 0) {
return false;
}
let midIdx = Math.floor(array.length / 2);
let leftHalf = array.slice(0, midIdx);
let rightHalf = array.slice(midIdx + 1);
if (target < array[midIdx]) {
return binarySearch(leftHalf, target);
} else if (target > array[midIdx]) {
return binarySearch(rightHalf, target);
} else {
return true;
}
}
// Returns the index or -1 if not found
function binarySearchIndex(array, target) {
if (!array.length) return -1;
const midIdx = Math.floor(array.length / 2);
const midEl = array[midIdx];
if (target < midEl) {
return binarySearchIndex(array.slice(0, midIdx), target);
} else if (target > midEl) {
// Since our recursive call will have new indices for the subarray, we have
// to adjust the return value to align it with the indices of our original
// array.
// If the recursive call returns -1, it was not found and we can immediately
// return -1
// If it was found in the subarray, we have to add on the number of elements
// that were removed from the beginning of our larger original array.
// For example, if we try to find 15 in an array of [5, 10, 15]:
// - Our first call to binarySearchIndex will check our middle element of 10
// - Since our target is greater, we will recursively call our search on
// elements to the right, being the subarray [15]
// - On our recursive call we found our target! It's index in this call is 0.
// - When we return 0 to where binarySearchIndex was called, we need to
// adjust it to line up with this larger array (the 0th element of this
// larger array is 5, but our target was at the 0th index of the subarray)
// - Since we sliced off 2 elements from the beginning before making our
// recursive call, we add 2 to the return value to adjust it back to line up
// with our original array.
const idxShift = binarySearchIndex(array.slice(midIdx + 1), target);
return idxShift === -1 ? -1 : idxShift + midIdx + 1;
} else {
return midIdx;
}
}function bubbleSort(array) {
let swapped = true;
while(swapped) {
swapped = false;
for (let i = 0; i < array.length - 1; i++) {
if (array[i] > array[i+1]) {
let temp = array[i];
array[i] = array[i + 1];
array[i + 1] = temp;
// The above three lines could also be in a helper swap function
// swap(array, i, i+1);
swapped = true;
}
}
}
return array;
}function selectionSort(arr) {
for (let i = 0; i < arr.length; i++) {
let minIndex = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j;
}
}
let temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
// The above three lines could also be in a helper swap function
// swap(arr, i, minIndex);
}
return arr;
}function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
let currElement = arr[i];
for (var j = i - 1; j >= 0 && currElement < arr[j]; j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = currElement;
}
return arr;
}// The merge function is what is combining our sorted sub-arrays
function merge(array1, array2) {
let merged = [];
// keep running while either array still contains elements
while (array1.length || array2.length) {
// if array1 is nonempty, take its the first element as ele1
// otherwise array1 is empty, so take Infinity as ele1
let ele1 = array1.length ? array1[0] : Infinity;
// do the same for array2, ele2
let ele2 = array2.length ? array2[0] : Infinity;
let next;
// remove the smaller of the eles from it's array
if (ele1 < ele2) {
next = array1.shift();
} else {
next = array2.shift();
}
// and add that ele to the new array
merged.push(next);
}
return merged;
}
// The mergeSort function breaks apart our input into smaller sub-arrays until
//we have an input of length <= 1, which is inherently sorted.
// Once we have a left and right subarray that's sorted, we can merge them
//together to get our sorted result of this sub-problem, passing the sorted
//version back up the call stack.
function mergeSort(array) {
if (array.length <= 1) {
return array;
}
let midIdx = Math.floor(array.length / 2);
let leftHalf = array.slice(0, midIdx);
let rightHalf = array.slice(midIdx);
let sortedLeft = mergeSort(leftHalf);
let sortedRight = mergeSort(rightHalf);
return merge(sortedLeft, sortedRight);
}function quickSort(array) {
if (array.length <= 1) {
return array;
}
let pivot = array.shift();
// This implementation uses filter, which returns a new array with any element
//that passes the criteria (ie the callback returns true).
// We also could have iterated over the array (array.forEach(el => ...)) and
//pushed each value into the appropriate left/right subarray as we encountered
// it.
let left = array.filter(el => el < pivot);
let right = array.filter(el => el >= pivot);
let leftSorted = quickSort(left);
let rightSorted = quickSort(right);
return [ ...leftSorted, pivot, ...rightSorted ];
// We also could have concatenated the arrays instead of spreading their contents
// return leftSorted.concat([pivot]).concat(rightSorted);
}| Sort Name | Time Complexity | Space Complexity |
|---|---|---|
| bubble | O(n^2) | O(1) |
| selection | O(n^2) | O(1) |
| insertion | O(n^2) | O(1) |
| merge | O(n log n) | O(n) |
| quick | *O(n log n)/O(n^2) | *O(n)/O(log n) |
function loglinear(n) {
if (n <= 1) return;
for (let i = 1; i <= n; i++) { // n calculations in each stack frame
console.log(n);
}
loglinear(n / 2); // log n number of stack frames
loglinear(n / 2);
}// O(n^2)
function quadratic(n) {
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= n; j++) {
console.log(`${i}, ${j}`);
}
}
}
// O(n^3)
function cubic(n) {
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= n; j++) {
for (let k = 1; k <= n; k++) {
console.log(`${i}, ${j}, ${k}`);
}
}
}
}// O(2^n)
function exponential_2n(n) {
console.log(n);
if (n === 1) return;
exponential_2n(n - 1);
exponential_2n(n - 1);
}
// O(3^n)
function exponential_3n(n) {
console.log(n);
if (n === 1) return;
exponential_3n(n - 1);
exponential_3n(n - 1);
exponential_3n(n - 1);
}// Standard Implementation
// Time Complexity: O(2^n)
// - For each call to fib, we have to make two branches, with n levels to this
// tree
function fib(n) {
if (n === 1 || n === 2) return 1;
return fib(n - 1) + fib(n - 2);
}
// Using memoization
// Time Complexity: O(n)
// - We only ever have to calculate fib(x) one time, every other time that we
// use it in a larger problem, the result is immediately accessible in our memo
// - The memo removes the repeated trees of calculations from our original code
function fib(n, memo = {}) {
if (n in memo) return memo[n]; // If we already calculated this value, return it
if (n === 1 || n === 2) return 1;
// Store the result in the memo first before returning
// Make sure to pass the memo in to your calls to fib!
memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
return memo[n];
}// Using tabulation
// Time Complexity: O(n)
// - We are iterating through an array directly related to the size of the input
// and performing a constant number of calculations at each step (adding our two
// previous values together and storing the result in the array).
function fib(n) {
// We create a table to track our values as we build them up
// We're making it n+1 here so that table[n] lines up with the nth fib number
// This is because arrays are zero-indexed.
// We could have used an array of length n, but we would have to remember that
// the nth fib number would then be stored at table[n-1]. Completely doable,
// but I think making them line up is more intuitive.
let table = new Array(n + 1);
// Seed our table with our starting values.
// Again, if we had a table of length n, we could have seeded table[0] = 1
// and table[1] = 1 and had the same final result with our indices shifted.
table[0] = 0;
table[1] = 1;
// Iterate through our input and fill out our table as we go.
for (let i = 2; i < table.length; i++) {
table[i] = table[i - 1] + table[i - 2];
}
// At the end, the final entry in our table is the result we are looking for.
// The table holds all of the sub-answers that we used to get to this result.
return table[n];
}Not on the assessment don’t worry