3

Recently I had an interview question as follows: Let us consider we have two sorted arrays of different length. Need to find the common elements in two arrays.

var a=[1,2,3,4,5,6,7,8,9,10];
var b = [2,4,5,7,11,15];
for(var i=0;i<a.length;i++){
    for(var j=0;j<b.length;j++){
        if(a[i]==b[j]){
            console.log(a[i],b[j])
        }
    }
}

I wrote like above. The interviewer said let now assume a have 2000 elements and b have 3000 elements. Then how you wrote in a more efficient way?

Please explain your answers with sample code. So I can understand more clearly.

Narayanan
  • 197
  • 1
  • 2
  • 10
  • arrays of object ? int ? strings ? – Nathan Gouy Sep 06 '18 at 11:56
  • Casn be there 2 or more same elements in one array? – Artem Arkhipov Sep 06 '18 at 11:56
  • 1
    Since they're sorted, [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm). Runs in `O(log n)` instead of `O(n^2)`. See also https://stackoverflow.com/questions/22697936/binary-search-in-javascript – Jared Smith Sep 06 '18 at 11:57
  • 1
    Possible duplicate of [Simplest code for array intersection in javascript](https://stackoverflow.com/questions/1885557/simplest-code-for-array-intersection-in-javascript) – Calvin Nunes Sep 06 '18 at 12:01
  • was thinking of a solution with Object (hash stuff), but : https://stackoverflow.com/questions/17295056/array-vs-object-efficiency-in-javascript array > object – Nathan Gouy Sep 06 '18 at 12:03
  • @JaredSmith I don’t think `O(log n)` is even possible. That implies that you don’t even look at every item of one of the two arrays; but then, how would you find all matching values? – Sebastian Simon Sep 06 '18 at 12:03
  • 2
    A complexity of O(_n_) is possible. Find the minimum value among both arrays, and find the next higher value for each item. Log matches along the way. – Sebastian Simon Sep 06 '18 at 12:06
  • Also you could take a look to [Interpolation Search](https://www.geeksforgeeks.org/interpolation-search/) – xpiero Sep 06 '18 at 12:11

10 Answers10

13
The easiest way!!

var a = [1,2,3,4,5,6,7,8,9,10];
var b = [2,4,5,7,11,15];

for(let i of a){
  if(b.includes(i)){
    console.log(i)
  }
}


--------- OR --------------

var c = a.filter(value => b.includes(value))
console.log(c)
md_salm
  • 459
  • 5
  • 9
5

Since the arrays are sorted, binary search is the key.

Basically, you're searching an item in an array.

You compare the item against the middle index of the array (length / 2)

If both are equal, you found it.

If item is inferior than the one at the middle index of the array, compare item against the index being at index length / 4 -> ((0 + length / 2) / 2), if it's inferior, at index ((length / 2) + length) / 2 (the middle of upper part) and so on.

That way, if in example you have to search item in a 40 000 length array, at worse, you find out that item isn't in the array with 16 comparisons :

I'm searching for "something" in an array with 40 000 indexes, minimum index where I can find it is 0, the maximum is 39999.

"something" > arr[20000]. Let's assume that. I know that now the minimum index to search is 20001 and the maximum is 39999. I'm now searching for the middle one, (20000 + 39999) / 2.

Now, "something" < arr[30000], it limits the search from indexes 20001 to 29999. (20000 + 30000) / 2 = 25000.

"something" > arr[25000], I have to search from 25001 to 29999. (25000 + 30000) / 2 = 27500

"something" < arr[27500], I have to search from 25001 to 27499. (25000 + 27500) / 2 = 26250

"something" > arr[26250], I have to search from 26251 to 27499. (26250 + 27500) / 2 = 26875

"something" < arr[26875], I have to search from 26251 to 26874. (26250 + 26875) / 2 = 26563

And so on... Of course, you have to round and stuff to avoid floating indexes

var iteration = 1;

function bSearch(item, arr)
{
    var minimumIndex = 0;
    var maximumIndex = arr.length - 1;
    var index = Math.round((minimumIndex + maximumIndex) / 2);

    while (true)
    {
        ++iteration;
        if (item == arr[index])
        {
            arr.splice(0, minimumIndex);
            return (true);
        }
        if (minimumIndex == maximumIndex)
        {
            arr.splice(0, minimumIndex);
            return (false);
        }
        if (item < arr[index])
        {
            maximumIndex = index - 1;
            index = Math.ceil((minimumIndex + maximumIndex) / 2);
        }
        else
        {
            minimumIndex = index + 1;
            index = Math.floor((minimumIndex + maximumIndex) / 2);
        }
    }
}

var arrA;
var arrB;

for (var i = 0; i < arrA.length; ++i)
{
    if (bSearch(arrA[i], arrB))
        console.log(arrA[i]);
}
console.log("number of iterations : " + iteration);
Cid
  • 14,968
  • 4
  • 30
  • 45
  • If you post working code, I will happily upvote this. – Jared Smith Sep 06 '18 at 12:08
  • No, binary search does help with finding *one* element in a sorted array, but not with comparing two sorted arrays. – Bergi Sep 06 '18 at 14:28
  • @Bergi I know right, but nothing stops you from looping the first array and call a binary search function. I'll edit my answer. – Cid Sep 06 '18 at 14:53
  • @Cid That's still pretty inefficient and not what the interviewer was looking for – Bergi Sep 06 '18 at 14:55
  • @Bergi that's still far more efficient than 2 nested loops. Check edited code. – Cid Sep 06 '18 at 15:08
  • @Bergi this answer is more fun and creative. My preferred way to learn and work. +1 from me. – גלעד ברקן Sep 06 '18 at 18:10
  • 1
    @Bergi furthermore, you are wrong about efficiency. This is the correct answer for the case of significantly unequal size. `constant * log2 x`, will quickly become much smaller than `constant + x`, as `x` gets larger. – גלעד ברקן Sep 06 '18 at 19:12
  • @גלעדברקן indeed, I tested it with array A having 2 000 indexes and array B having 200 000 indexes. ~ 33 000 iterations are needed each time I'm running it. With array A length of 20 000 and array B length of 2 000 000, ~390 000 iterations are needed. Far more efficient than a O(n + m) solution. – Cid Sep 07 '18 at 06:49
5

You could use a nested approach by checking the index of each array and find the values by incrementing the indices. If equal values are found, increment both indices.

Time complexity: max. O(n+m), where n is the length of array a and m is the length of array b.

var a = [1, 2, 3, 4, 5, 6, 8, 10, 11, 15], // left side
    b = [3, 7, 8, 11, 12, 13, 15, 17],     // right side
    i = 0,                                 // index for a
    j = 0;                                 // index for b

while (i < a.length && j < b.length) {     // prevent running forever
    while (a[i] < b[j]) {                  // check left side
        ++i;                               // increment index
    }
    while (b[j] < a[i]) {                  // check right side
        ++j;                               // increment
    }
    if (a[i] === b[j]) {                   // check equalness
        console.log(a[i], b[j]);           // output or collect
        ++i;                               // increment indices
        ++j;
    }
}
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • This works like a charm only if each element is unique – Cid Sep 06 '18 at 12:36
  • 1
    @Cid, if are duplicates in the same array, you need to add another while loops until the same value is gone. – Nina Scholz Sep 06 '18 at 12:43
  • Merge-like approach is the best possible method. O(n+m) complexity , no additional storage. – MBo Sep 06 '18 at 15:19
  • 1
    @MBo the efficiency of this answer would be surpassed by binary search for the case of significantly unequal size. `constant * log2 x`, will quickly become much smaller than `constant + x`, as `x` gets larger. – גלעד ברקן Sep 06 '18 at 19:14
  • @גלעד ברקן You are considering very exotic edge case when `log2(m) <=2` – MBo Sep 07 '18 at 01:58
  • 1
    @MBo I'm not sure what you mean. 2000 * log2 40000 ≈ 30000, for example. 2000 * log2 400000 ≈ 37000. How is that exotic? – גלעד ברקן Sep 07 '18 at 02:06
  • 1
    @גלעד ברקן Aha, now I did catch. I accidentally thought about reverse situation (search long list elements in small list). So it is worth to choose a method depending on size ratio. – MBo Sep 07 '18 at 02:14
4

since both arrays are sorted just save the lastest match index . then start your inner loop from this index .

var lastMatchedIndex = 0;
for(var i=0;i<a.length;i++){
    for(var j=lastMatchIndex ;j<b.length;j++){
        if(a[i]==b[j]){
            console.log(a[i],b[j]);
            lastMatchedIndex = j;
            break;
        }
    }
}

=================

UPDATE :

As Xufox mentioned in comments if a[i] is lower than b[i] then u have break loop since it has no point to continue the loop .

var lastMatchedIndex = 0;
for(var i=0;i<a.length;i++){
    if(a[i]<b[i]){
        break;
    }   
    for(var j=lastMatchIndex ;j<b.length;j++){
        if(a[i]==b[j]){
            console.log(a[i],b[j]);
            lastMatchedIndex = j;
            break;
        }
        if(a[i]<b[j]){
            lastMatchedIndex = j;
            break;
        }         
    }
}
SoroushNeshat
  • 656
  • 5
  • 11
1

An optimal strategy would be one where you minimize the amount of comparisons and array readings.

Theoretically what you want is to alternate which list you are progressing through so as to avoid unnecessary comparisons. Giving that the lists are sorted we know that no number to the left of any index in a list can ever be smaller than the current index.

Assuming the following list A = [1,5], list B = [1,1,3,4,5,6] and indexes a and b both starting at 0, you would want your code to go like this:

A[a] == 1, B[b] == 1
A[a] == B[b] --> add indexes to results and increase b (B[b] == 1)
A[a] == B[b] --> add indexes to results and increase b (B[b] == 3)
A[a] < B[b] --> don't add indexes to results and increase a (A[a] == 5)
A[a] > B[b] --> don't add indexes to results and increase b (B[b] == 4)
A[a] > B[b] --> don't add indexes to results and increase b (B[b] == 5)
A[a] == B[b] --> add indexes to results and increase b (B[b] == 6)
A[a] < B[b] --> don't add indexes to results and increase a (A is at the end, so we terminate and return results)

Below is my JavaScript performing the above described algorithm:

//Parameters
var listA = [];
var listB = [];
//Parameter initialization
(function populateListA() {
    var value = 0;
    while (listA.length < 200) {
        listA.push(value);
        value += Math.round(Math.random());
    }
})();
(function populateListB() {
    var value = 0;
    while (listB.length < 300) {
        listB.push(value);
        value += Math.round(Math.random());
    }
})();
//Searcher function
function findCommon(listA, listB) {
    //List of results to return
    var results = [];
    //Initialize indexes
    var indexA = 0;
    var indexB = 0;
    //Loop through list a
    while (indexA < listA.length) {
        //Get value of A
        var valueA = listA[indexA];
        var result_1 = void 0;
        //Get last result or make a first result
        if (results.length < 1) {
            result_1 = {
                value: valueA,
                indexesInA: [],
                indexesInB: []
            };
            results.push(result_1);
        }
        else {
            result_1 = results[results.length - 1];
        }
        //If higher than last result, make new result
        //Push index to result
        if (result_1.value < valueA) {
            //Make new object
            result_1 = {
                value: valueA,
                indexesInA: [indexA],
                indexesInB: []
            };
            //Push to list
            results.push(result_1);
        }
        else {
            //Add indexA to list
            result_1.indexesInA.push(indexA);
        }
        //Loop through list b
        while (indexB < listB.length) {
            //Get value of B
            var valueB = listB[indexB];
            //If b is less than a, move up list b
            if (valueB < valueA) {
                indexB++;
                continue;
            }
            //If b is greather than a, break and move up list a
            if (valueB > valueA) {
                break;
            }
            //If b matches a, append index to result
            result_1.indexesInB.push(indexB);
            //Move up list B
            indexB++;
        }
        //Move up list A
        indexA++;
    }
    //Return all results with values in both lines
    return results.filter(function (result) { return result.indexesInB.length > 0; });
}
//Run
var result = findCommon(listA, listB);
//Output
console.log(result);
Emil S. Jørgensen
  • 6,216
  • 1
  • 15
  • 28
0

We could iterate one array and find the duplicate in the other, but each time we find a match, we move to the matched element + 1 for the next iteration in the nested loop. It works because both arrays are sorted. So each match the array to compare is shorter (from left to right).

We could also break the nested loop when the element of the second array is greater than the first (it's shorter from right to left), because we will never find a match (since the array is ordered, there are only greater values remaining), here and example finding duplicates in two arrays of 10k elements, takes roughly 15 miliseconds:

var arr = [];
var arr2 = [];

for(let i = 0; i<9999; i++){
    arr.push(i);
    arr2.push(i+4999)
}

var k = 0;//<-- the index we start to compare
var res = [];

for (let i = 0; i < arr2.length; i++) {
  for (let j = k; j < arr.length; j++) {
    if (arr2[i] === arr[j]) {
      res.push(arr2[i]);
      k = j + 1;//<-- updates the index
      break;
    } else if (arr[j] > arr2[i]) {//<-- there is no need to keep going
      break;
    }
  }
}

console.log(res.length)

I did not print res, because it has 5000 elements.

Emeeus
  • 5,072
  • 2
  • 25
  • 37
0

You can build a hash with first array (irrespective of they are sorted or not) and iterate the second array and check for existence in the hash!

let arr1 = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150],
  arr2 = [15,30,45,60,75,90,105,120,135,150,165]
  hash = arr1.reduce((h,e)=> (h[e]=1, h), {}), //iterate first array once
  common = arr2.filter(v=>hash[v]); //iterate secod array once
  
  console.log('Cpmmon elements: ', common);
Koushik Chatterjee
  • 4,106
  • 3
  • 18
  • 32
0

Not sure but this may help

let num1 = [2, 3, 6, 6, 5];
let num2 = [1, 3, 6, 4];
var array3 = num1.filter((x) => {
  return num2.indexOf(x) != -1
})
console.log(array3);
Christopher Bradshaw
  • 2,615
  • 4
  • 24
  • 38
tonmoy122
  • 41
  • 3
0

I sometimes find it convenient to turn one list into a hashset.

var hashA = {};
for(var i=0; i<a.length; i++) {hashA[a[i]] = true;}

then you can search the hashset.

for(var i=0; i<b.length; i++) {if(hashA[b[i]]) {console.log(b[i]);}}

This isnt as fast as the binary search of course because you have to take time to build the hashset, but its not bad, and if you need to keep the list and do a lot of future searching it might be the best option. Also, I know javascript objects arent really just hashsets, its complicated, but it mostly works pretty well.

Honestly though, for 3000 items I wouldnt change the code. Thats still not big enough to be an issue. That will run in like 30ms. So it also depends on how often its going to run. Once an hour? Forget about it. Once per millisecond? Definitely gotta optimize that.

rocketsarefast
  • 4,072
  • 1
  • 24
  • 18
-8

if we are talking about the algorithm to find common elements between two array, then here is my opinion.

function common(arr1, arr2) {
 var newArr = [];
 newArr = arr1.filter(function(v){ return arr2.indexOf(v) >= 0;}) 
 newArr.concat(arr2.filter(function(v){ return newArr.indexOf(v) >= 0;}));
 return newArr;
}

but if you are going to think on performance also, then you should try another ways also.

first check the performance for javascript loop here, it will help you to figure out best way

https://dzone.com/articles/performance-check-on-different-type-of-for-loops-a

https://hackernoon.com/javascript-performance-test-for-vs-for-each-vs-map-reduce-filter-find-32c1113f19d7

Arif Rathod
  • 578
  • 2
  • 13
  • 2
    This leads to the exact same complexity (if not worse) – Nathan Gouy Sep 06 '18 at 12:04
  • its better then creating loop inside loop. because if you use loop inside loop then loop count is 2000*3000 (array length) and in my code it will be 2000 + 3000. do have any other idea? – Arif Rathod Sep 06 '18 at 12:07
  • 2
    Your code is not 2000 + 3000 (i.e. linear), using `.indexOf` just hides the quadratic-ness. It's still there. – Jared Smith Sep 06 '18 at 12:10
  • but i have shared my opinion about the question. i have checked both function timing. my function is working faster then loop function. – Arif Rathod Sep 06 '18 at 12:13
  • 3
    @ArifRathod so what? It's not faster *in big O terms*. It's still quadratic: a constant factor improvement is not relevant to an interview question about algorithmic complexity. Let me tackle this a different way: if the arrays were 20 million elements and 30 million elements respectively, still think your answer would be fast enough? – Jared Smith Sep 06 '18 at 12:17
  • @JaredSmith, please suggest better way to do this. i m also want to know the right answer – Arif Rathod Sep 07 '18 at 06:28
  • @ArifRathod First, understand that the "right" answer is context-dependent. If I was simply trying to write short, maintainable code I would use the short, naive quadratic version of finding the union of the two sets. But this question was asked in the context of an *interview question about performance*, and tagged with the `algorithm` tag, meaning it's specifically about finding an efficient (i.e. *not* `O(n^2)`) algorithm. As for how to accomplish it, there are already two answers with multiple upvotes that do a good job of outlining the strategy from Nina Sholz and Soroush Neshat. – Jared Smith Sep 07 '18 at 10:49