FYI One: This is not all there is to Big-O
/* Let's start out easy */
function foo(arr) {
var sum = 0;
var product = 1;
for (var i = 0; i < arr.length; i++)
sum += arr[i];
for (var j = 0; j < arr.length; j++)
product *= arr[i];
console.log(sum * product);
}
/* Piece of cake */
function foo(arr) {
var sum = 0; // O(1)
var product = 1; // O(1)
for (var i = 0; i < arr.length; i++) // O(arr)
sum += arr[i]; // O(1)
for (var j = 0; j < arr.length; j++) // O(arr)
product *= arr[i]; // O(1)
console.log(sum * product); // O(1)
}
// O(1) + O(1) + (O(arr) * O(1)) + (O(arr) * O(1)) + O(1)
// O(3 * arr) => O(arr) => O(n)
/* Another softball */
function bar(arr) {
for (var i = 0; i < arr.length; i++)
for (var j = 0; j < arr.length; j++)
console.log(arr[i] + arr[j]);
}
/* All good! */
function bar(arr) {
for (var i = 0; i < arr.length; i++) // O(arr)
for (var j = 0; j < arr.length; j++) // O(arr)
console.log(arr[i] + arr[j]); // O(1)
}
// O(arr) * O(arr) * O(1) => O(arr^2) => O(n^2)
/* Don't worry, you got this */
function baz(arrA, arrB) {
for (var i = 0; i < arrA.length; i++)
for (var j = 0; j < arrB.length; j++)
console.log(arrA[i] + arrB[j]);
}
/* See that wasn't so bad */
function baz(arrA, arrB) {
for (var i = 0; i < arrA.length; i++) // O(arrA)
for (var j = 0; j < arrB.length; j++) // O(arrB)
console.log(arrA[i] + arrB[j]); // O(1)
}
// O(arrA) * O(arrB) * O(1) => O(nm)
/* Wait, is this a trick? */
function foobar(arr) {
for (var i = 0; i < arr.length; i++)
for (var j = i + 1; j < arr.length; j++)
console.log(arr[i] + arr[j]);
}
/* Okay, not so bad... */
function foobar(arr) {
for (var i = 0; i < arr.length; i++) // O(arr)
for (var j = i + 1; j < arr.length; j++) // on average, between 1 and arr
console.log(arr[i] + arr[j]); // O(1)
}
// O(arr) * O(arr-ish) * O(1) => O(n^2)
/* As the size of arr gets large,
it stops mattering that O(arr-ish) is actually less than O(arr).
The shape of the growth curve will still be similar.
*/
/*
7
/ \
4 9
/ \ / \
1 6 8 12
Seven elements altogether
Three levels deep
*/
/* Let's take it to the limit! */
function fib (n) {
if (n === 1 || n === 0) return n;
else return fib(n - 1) + fib(n - 2);
}
/* This is why fib is so slow! */
/*
fib(4)
/ \
fib(3) fib(2)
/ \ / \
fib(2) fib(1) fib(1) fib(0)
/ \
fib(1) fib(0)
our input is equal to 4: n = 4
we go four levels deep, so depth = n
we branch twice with each recursive call
therefore, runtime is O(2^n)!
*/
/* What if we throw some dynamic programming in there? */
function fib (n, memo) {
if (!memo) var memo = {};
if (n === 1 || n === 0) return n;
else if (memo[n]) return memo[n];
else memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
return memo[n];
}
/* Such quicker, much dynamic, wow! */
/*
fib(4)
/ \
fib(3) fib(2)
/ \ / \
fib(2) fib(1) fib(1) fib(0)
/ \
fib(1) fib(0)
1. fib(4) = fib(3) + fib(2)
/
2. fib(3) = fib(2) + fib(1)
/
3. fib(2) = fib(1) + fib(0) = memo[2]
4. fib(3) = memo[2] + fib(1) = memo[3]
5. fib(4) = memo[3] + fib(2) = memo[3] + memo[2]
*/
/*
That entire second branch got taken out of the picture
Every step ends up being in constant time, which we only do a maximum of n times
Using a memo cuts runtime down to O(n)!
*/
/* Basic example of calculating space complexity */
function (arr) {
var arrOfarrs = [];
for (var i = 0; i < arr.length; i++)
arrOfarrs.push(arr);
return arrOfarrs;
}
/*
This would be space O(n^2). Not sure why you would do this, though...
*/
/* Memoized fibonacci revisited */
function fib (n, memo) {
if (!memo) var memo = {};
if (n === 1 || n === 0) return n;
else if (memo[n]) return memo[n];
else memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
return memo[n];
}
/*
This is a lot quicker than the non-memoized version, but remember that it's still recursive,
so we will eventually have n calls on the call stack. We also have the memo,
but it ends up not mattering much. It will always contain a little less than n items.
Therefore, space complexity is O(n + n-ish) => O(n)
...which is the same as the non-memoized version!
*/
/* Sorting an array of strings */
function sortedStrings (arr) {
for (var i = 0; i < arr.length; i++)
arr[i].sort(); // Let's say that .sort is O(n log n)
}
// Fun fact: different browsers have different implementations for Array.prototype.sort!
/* Sorting an array of strings */
/* The key to understanding this is that we have two algorithms with two different inputs
sortedStrings takes an array with an array input, with a length of say 'n'
Array.prototype.sort takes a string input, with a length of say 's'
*/
function sortedStrings (arr) {
for (var i = 0; i < arr.length; i++) // O(n)
arr[i].sort(); // O(s log s)
}
// O(n) * O(s log s) => O(n * s log s)