I need help with the following problems on determining what the Big O is of each function.
For problem one, I've tried O(log(n)) and O(n). I figured the function was linear or in other words, for N elements we will require N iterations.
For problem two, I've tried O(n^2). I figured for this kind of order, the worst case time (iterations) is the square of the number of inputs. The time grows exponentially related to the number of inputs.
For problem three, I've tried O(n^2) and O(1).
Problem One:
function foo(array){
let sum = 0;
let product = 1;
for (let i = 0; i < array.length; i++){
sum += array[i]
}
for(let i = 0; i < array.length; i++){
product *= array[i];
}
consle.log(sum + ", " + product);
}
Problem Two:
function printUnorderedParis(array){
for(let i = 0; i < array.length; i++){
for(let j = i + j; j < array.length; j++){
console.log(array[i] + ", " + array[j]);
}
}
}
Problem Three:
function printUnorderedPairs(arrayA, arrayB){
for(let i = 0; i < arrayA.length; i++){
for(let j = 0; i < arrayB.length; j++){
for(let k = 0; k < 100000; k++){
console.log(arrayA[i] + ", " + arrayB[j]);
}
}
}
}
I expected my initial thoughts to be right, but maybe I'm having a hard time grasping Big O.
You're correct that it's O(n). You have two loops, they each perform array.length iterations. You could even combine them into a single loop to make it more obvious.
for (let i = 0; i < array.length; i++) {
sum += array[i];
product *= array[i];
}
You're correct, it's O(n^2). The nested loops perform array.length * array.length iterations.
EDIT -- see my comment above asking whether this problem is copied correctly.
This is also O(n^2). The third level of nested loop doesn't change the complexity, because it performs a fixed number of iterations. Since this doesn't depend on the size of the input, it's treated as a constant. So as far as Big-O is concerned, this is equivalent to Problem 2.
Well, you kind of answered your questions, but here we go:
In the first problem, you have two for loops, each of them iterating over the entire array. For a general array of size n, you'll have O(2n) or simply O(n) since we can let go of constants. There isn't any reasons this would be O(log(n)).
For the second one, I think there is a mistake. The statement j = i + j is not valid, and you'll get Uncaught ReferenceError: j is not defined. However, let's say the statement is actually let j = i. Then, we have:
i, iterating over the entire array, starting from the first element and going all the way to the last one
j, starting from i and going all the way to the last element
With this information, we know that for i = 0, j will iterate from 0 to n (n being the array's length), so n steps. For i=1, j will go from 1 to n, so n-1 steps. Generalizing, we are going to have a sum: n + (n - 1) + (n - 2) + ... + 1 + 0 = n * (n + 1) / 2 = 1/2 * (n^2 + n). So, the complexity is O(1/2 * (n^2 + n) = O(n^2 + n) = O(n). So you were correct.
For the third problem, the answer is O(n^2) and not O(1). The reasoning is very close to the one I made for the second one. Basically, the inner k will be executed 100000 times for every j iteration, but the number of iteration does not depend of n (the size of the array).
It's easy to see that:
for i = 0, j will go from 0 to n (last value for which j body will be executed being j = n - 1).
for j = 0, we will to 100k iterations
for j = 1, another 100k iterations
...
for j = n - 1, another 100k iterations
The entire j loop will make n * 100000 = 100000n iterations.
For i = 1, the same behaviour:
for j = 0, we will to 100k iterations
for j = 1, another 100k iterations
...
for j = n - 1, another 100k iterations,
getting another 100000n iterations.
In the end, we end up with 100000n + 100000n + ... + 100000n (n times) = sum(i = 0, n, 100000n) = n * 100000n = 100000 * n^2. So, the big O is O(100000 * n^2) = O(n^2).
Cheers!
Related
I have a quadratic sequence in which I need to use to loop over, however, I am not sure how to execute this kind of logic... I want to use the quadratic sequence to count up to a given number <= n. The problem is that I have to give my quadratic sequence for loop some number to count and iterate up to as well... I know it's possible to iterate by 2s, 3s, 4s, etc. by doing things like i += 2, is it possible to use a quadratic expression as well? Is there a method using Javascript in which will allow me to iterate through quadratic sequences? I hope this is clear, thanks!
Here's my code:
The quadratic sequence is the sequence being console logged.
The first 14 terms are: 1,3,6,10,15,21,28,36,45,55,66,78,91,105
for (let i = 1; i <= 14; i++) {
let a = i;
let nthTerm = a - 1;
let b = 1 / 2;
let quadraticSequence = b * a ** 2 + b * a;
console.log(`${nthTerm} ${quadraticSequence}`);
const num = 93;
// use quadratic sequence to count up to a number <= 93;
for (i = 0; i <= num; i++quadraticSequence) {
console.log(i);
}
}
The result should console.log a count 0 > 105. I know the second for loop is not correct, but I'm not quite sure how to approach a proper attempt.
So instead of counting like, 1,2,3,4,5,6,7,8,9... It should count 1,3,6,10,15,21,28,36,45,55... Ultimately, I am trying to count up to a given number <= n (i.e. 93) through the quadratic sequence instead of the usually, 1,2,3,4,5, sequence.
let num = 93;
for (let i = 1, j = 2; i < 100; i += j, j += 1) {
console.log(i);
for (let k = 0; k <= num; k += i) {
console.log(k);
}
}
This will produce the sequence 1, 3, 6, 10, 15, 21, 28, 36, ... and then use each of them in a seperate for loop as step size.
An alternative interpretation would be using the 0th element in the quadratic series as the first step size and the next element as the next step size, etc.
for (let i = 1, j = 2, k = 0; k < 1000; k += i, i += j, j += 1) {
console.log(`Current, step: ${k}, ${i}`);
}
This should do that
How would I get a standard for loop to output in pairs or other groups (like three's of four's) with the output shifting up one after the last digit of the group?
for(var i = 0: i < 8; i++){
console.log(i)
}
so instead of the output being; 0,1,2,3,4,5,6,7
In pairs it would be; 0,1,1,2,2,3,3,4
or if it went up in groups of four; 0,1,2,3,1,2,3,4
I did try doing something like this, but instead of going up in two's every time I need the loop to output the first 2 digits move up one then output the next two ect...
for(var i = 0: i < 8; i+= 2){
console.log(i)
}
Hope that makes sense
For each case you would need to come up with the right formula based on i:
so instead of the output being; 0,1,2,3,4,5,6,7 In pairs it would be; 0,1,1,2,2,3,3,4
for (let i = 1; i < 9; i++) {
console.log(i >> 1); // this bit shift is integer division by 2
}
or if it went up in groups of four; 0,1,2,3,1,2,3,4
for (let i = 0; i < 8; i++) {
// Perform division by 4 and add remainder to that integer quotient
console.log((i >> 2) + (i % 4));
}
You could work with a variable inside the loop to determine the index. This way you can specify how many times you want the loop to run:
for(let index = 0; index < 8; index++) {
const currentIndex = index - (index >> 1);
console.log(currentIndex);
}
It also makes it easy to implement it as immutable:
const array = new Array(8).fill(0).map((entry, index) => index - (index >> 1));
console.log(array);
I think a function like below where we specify the total n and the chunksize after which you want to increase a single step might work for us :-
function getByChunkSteps(n,chunkSize){
let step = -1;
let output = [];
for(let index = 0;index < n;index++){
if(index%chunkSize===0){
step+=1;
}
output.push((index%chunkSize)+step);
}
return output;
}
console.log(getByChunkSteps(10,2));
console.log(getByChunkSteps(8,4));
console.log(getByChunkSteps(9,3));
Lets say we have an array of 200 000 elements for example...
Now we want to iterate it in different ways and check the fastest one. I've heard that if we will save array.length in variable before loop we will reduce execution time, so i tried the code below
let sum = 0
for (let i = 0; i < arr.length; ++i) sum += arr[i]
against
let sum = 0
for (let i = 0, l = arr.length; i < l; ++i) sum += arr[i]
But i got the same result as if in both cases js reads length value just once in the very beginning.
Then i decided to check, what if during loop we will change an array, removing last element.
let sum = 0
for (let i = 0; i < arr.length; ++i) {
sum += arr[i]
if (i === 100) arr.pop()
}
against
let sum = 0
for (let i = 0, l = arr.length; i < l; ++i) {
sum += arr[i]
if (i === 100) arr.pop()
}
So i expected that second case now should work faster because in first case js inevitably should check array.length each time and i was much suprised that it is not works faster but even slower - from 10 to 15 %. For me it is unexplainable. Any ideas?
Tests: https://jsbench.me/tfkefwjuw2
The problem is that
let sum = 0
for (let i = 0, l = arr.length; i < l; ++i) {
sum += arr[i]
if (i === 100) arr.pop()
}
is now incorrect, as it loops beyond the end of the array. The sum will be NaN in the end. The correct solution would have l = arr.length - 1 so that this doesn't happen.
Now why does it becomes so slow? Because array accesses in javascript are only fast (get compiled to a fast path with pointer addressing) when the element exists. When you miss, the code will get de-optimised. Since JSbench runs the same code multiple times, so even if the deoptimisation happens only at the last of 200000 iterations, the subsequent runs will be much slower.
See this talk for details, which even explicitly spells out "Avoid out-of-bounds reads" on one slide. In general, don't use non-idiomatic code to improve performance. i < arr.length is idiomatic, and JS engines will optimise it well.
How can I make use of binary search for improving my algorithms time complexity?
I'm reviewing time complexity for some interviews & I'm having trouble making my algorithm more time efficient. This is my brute force solution for the 3-Sum problem: how many triples sum to exactly 0? Background: I don't have a CS degree.
//BRUTE FORCE SOLUTION: N^3
var threeSum = function(list){
var count = 0;
//checking each triple
for(var i = 0; i < list.length; i++){
for(var j = i+1; j < list.length; j++){
for(var k = j+1; k < list.length; k++){
if(list[i] + list[j] + list[k] === 0){count++;}
}
}
}
return count;
};
//binary search code
var binarySearch = function(target, array){
var lo = 0;
var hi = array.length - 1;
//base case
while(lo <= hi){
var mid = Math.floor( lo + (hi - lo) / 2 );
if(target === array[mid]) return mid;
if(target < array[mid]){
hi = mid - 1;
}
if(target > array[mid]){
lo = mid + 1;
}
}
// value not found
return -1;
}
I was reviewing an algorithms course online from Princeton & the professor noted that this algorithm could be made more efficient with use of a binary search algorithm.
According to the professor we would:
sort the list
for each pair of numbers array[ i ] & array[ j ] binary search for -(array[ i ] + array[ j ])
However, I'm having trouble understanding how binary search comes in to solve the problem. Here is a slide from the lecture, which I'm still trying to understand, but maybe useful to others:
I'm sure there a several efficient solutions out there: feel free to chime in with your implementation as it may help me and other future readers. Thanks
However, I'm having trouble understanding how binary search comes in to solve the problem.
This is how the n^2 log(n) algorithm works:
Sort the list in O(nlogn) time
Find all pairs of numbers (i,j), which is O(n^2) runtime.
Then, for each pair (i,j), it finds a number k where k = sum - j - i. This is constant time O(1)
The algorithm checks to see if each k exists, since the tuple (i,j,k) would sum to sum. To do this, do a binary search which takes log(n) time.
The final runtime would be O(nlogn) + O(logn * n^2) = O(n^2logn)
An alternative (and faster) solution would be to replace the sorting portion with a hashtable. Then, lookup of value k would take O(1) time instead of logn
The problem that the binary search approach is trying to solve is reducing the complexity of a cubic algorithm (this is your brute force algorithm) into a ~ N^2 log N algorithm.
As other commenters pointed out we know that when the following statement: list[i] + list[j] + list[k] == 0 is true than we found a 3SUM result. This is the same as saying that -(list[i] + list[j]) == list[k]. So the goal of the algorithm is to check for each i index and j index pair that there is a corresponding k index which satisfies the previous equation. Binary search can find those k indices in ~log N time. Hence the overall order of growth being ~N^2 log N (the outer for loops correspond to the N^2 part).
As for the implementation in javascript I would do it like this:
var threesum = function(list) {
list.sort(function(a,b){return a - b});
console.log(list);
var cnt = 0;
for(var i=0; i<list.length; i++) {
for(var j=i+1; j<list.length; j++) {
var k = bsearch(-(list[i]+list[j]), list);
if (k!= null && k > j) {
console.log("[i=%d], [j=%d], [k=%d]", i,j,k);
cnt++;
}
}
}
return cnt;
};
var bsearch = function(key, a) {
var lo = 0;
var hi = a.length-1;
while (lo <= hi) {
var mid = lo + Math.floor((hi - lo) / 2);
if (a[mid] < key) {
lo = mid + 1;
} else if (a[mid] > key) {
hi = mid - 1;
} else {
return mid;
}
}
return null;
};
threesum([41, 48, 31, 32, 34, 38, 1, -9, 12, 13, 99, 5, -65, 8, 3, -3])
The algorithm basically works in the following way:
Sort the array (worst-case O(n ^ 2), depending upon sorting algorithm)
Generate all pairs of numbers - takes O(n ^ 2)
for each pair (i , j), there might exist k, such that 0 = i + j + k.kis simply-(i + j), thus we can easily look it up by binary search inO(log n). Cases wherei < k < j` doesn't hold are avoided to exclude duplicates.
Thus total time-complexity is O(n ^ 2 log n).
const threeSum =(array,target)=>{
let result =[]
array = array.sort((a,b)=> a-b)
for(let i=0; i < array.length-2; i++){
let left = i+1;
let right = array.length -1;
while(left < right){
let sum = array[i]+ array[left]+ array[right];
if(sum === target){
result.push([array[i],array[left], array[right]]);
left++;
right--
}else if(sum < target){
//sum is lower than target so increment left pointer
left++;
}else if(sum > target){
//sum is greater than target so increment right pointer
right--;
}
}
}
//return the list
return result;
}
let a = [12, 3, 1, 2, -6, 5, -8, 6];
console.log(threeSum(a, 0));
Time Complexity: O(n^2)
Space Complexity: O(1)
Here is the code in question:
var L1 = [];
var Q1 = [];
function populateListOne() {
var limit = prompt("please enter a number you would like to fill L1 to.");
for (i = 2; i <= limit; i++) {
L1[i] = i;
}
for (n = 2; n <= L1.length; n++) {
var count = 2;
if (n == count) {
var index = L1.indexOf(n);
L1.splice(index, 1);
Q1[n] = n;
count = count + 1;
}
for (j = 0; j <= L1.length; j++) {
if (L1[j] % 2 == 0) {
var secondIndex = L1.indexOf(j);
L1.splice(secondIndex, 1);
}
}
}
document.getElementById("demo").innerHTML = "iteration " + "1" + ": " + L1 + " Q1 = " + Q1;
}
I’m currently working on a homework assignment where I have to setup a queue. All is explained in my JSFiddle.
Problem description
Essentially, the part I’m stuck on is iterating through each instance of the array and then taking the value out if the modulus is identical to 0. However, as you can see when I run the program, it doesn’t work out that way. I know the problem is in the second for loop I just don’t see what I’m doing wrong.
The way I read it is, if j is less than the length of the array, increment. Then, if the value of the index of L1[j] modulus 2 is identical to 0, set the value of secondIndex to whatever the index of j is. Then splice it out. So, theoretically, only numbers divisible by two should be removed.
Input
A single number limit, which will be used to fill array L1.
L1 will be initialized with values 2, 3, ... limit.
Process
Get the starting element of array L1 and place it in array Q1.
Using that element, remove all values in array L1 that are divisible by that number.
Repeat until array L1 is empty.
You're going to have issues with looping over an array if you're changing the array within the loop. To help with this, I tend to iterate from back to front (also note: iterate from array.length - 1 as the length element does not exist, arrays are key'd from 0):
for(j = L1.length - 1; j >=0 ; j--)
For your first loop, you miss the elements L1[0] and L1[1], so I would change the first loop to:
L1 = [];
for(i = 2; i <= limit; i++)
{
L1.push(i);
}
In this section:
for(j = 0; j <= L1.length; j++){
if(L1[j] % 2 == 0)
{
var secondIndex = L1.indexOf(j);
L1.splice(secondIndex, 1);
}
}
you should splice with j instead of secondIndex.
Change L1.splice(secondIndex, 1); to L1.splice(j, 1);
Array indices and putting entries
You initial code used an array that was initialized to start at index 2. To avoid confusion, of what index to start at, start with index 0 and iterate until array.length instead of a predefined value limit to ensure that you go through each element.
The following still works but will be more of a headache because you need remember where to start and when you will end.
for (i = 2; i <= limit; i++) {
L1[i] = i; // 'i' will begin at two!
}
Here's a better way:
for (i = 2; i <= limit; i++) {
// 'i' starts at 2 and since L1 is an empty array,
// pushing elements into it will start index at 0!
L1.push(i);
}
Use pop and slice when getting values
When you need to take a peek at what value is at the start of your array, you can do so by using L1[0] if you followed my advice above regarding array keys.
However, when you are sure about needing to remove the starting element of the array, use Array.slice(idx, amt). idx specifies which index to start at, and amt specifies how many elements to remove beginning at that index (inclusive).
// Go to 1st element in L1. Remove (1 element at index 0) from L1.
var current = L1.splice(0, 1);
Use the appropriate loops
To make your life easier, use the appropriate loops when necessary. For loops are used when you know exactly how many times you will iterate. Use while loops when you are expecting an event.
In your case, 'repeat until L1 is empty' directly translates to:
do {
// divisibility checking
} while (L1.length > 0);
JSFiddle
Here's a complete JS fiddle with in-line comments that does exactly what you said.