用于在两个数组中查找公共元素的 Javascript 程序
Posted
技术标签:
【中文标题】用于在两个数组中查找公共元素的 Javascript 程序【英文标题】:Javascript Program for find common elements in two array 【发布时间】:2019-02-11 16:25:50 【问题描述】:最近我有一个面试问题如下: 让我们考虑我们有两个不同长度的排序数组。需要在两个数组中找到共同的元素。
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])
我像上面那样写的。面试官说现在假设 a 有 2000 个元素,b 有 3000 个元素。那你是怎么写得更有效率的呢?
请用示例代码解释您的答案。这样我可以更清楚地理解。
【问题讨论】:
对象数组?诠释?字符串? 一个数组中是否有 2 个或多个相同的元素? 因为它们已排序,binary search。在O(log n)
而不是 O(n^2)
中运行。另见***.com/questions/22697936/…
Simplest code for array intersection in javascript的可能重复
O(n) 的复杂度是可能的。找到两个数组中的最小值,然后为每个项目找到下一个更高的值。一路记录匹配。
【参考方案1】:
我有时发现将一个列表转换为哈希集很方便。
var hashA = ;
for(var i=0; i<a.length; i++) hashA[a[i]] = true;
然后你可以搜索哈希集。
for(var i=0; i<b.length; i++) if(hashA[b[i]]) console.log(b[i]);
这当然不如二分搜索快,因为你必须花时间来构建哈希集,但它还不错,如果你需要保留列表并在未来进行大量搜索,它可能是最好的选择.另外,我知道 javascript 对象不仅仅是哈希集,它很复杂,但大多数情况下效果都很好。
不过,老实说,对于 3000 个项目,我不会更改代码。那还不足以成为一个问题。这将在 30 毫秒内运行。因此,它还取决于它运行的频率。一小时一次?忘掉它。每毫秒一次?绝对要优化它。
【讨论】:
【参考方案2】: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)
【讨论】:
【参考方案3】:不确定,但这可能会有所帮助
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);
【讨论】:
【参考方案4】:如果我们谈论的是在两个数组之间找到共同元素的算法,那么这是我的看法。
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;
但如果你还要考虑性能,那么你也应该尝试其他方法。
首先在此处检查 javascript 循环的性能,它将帮助您找出最佳方法
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
【讨论】:
这会导致完全相同的复杂性(如果不是更糟的话) 它比在循环内创建循环更好。因为如果你在循环内使用循环,那么循环计数是 2000*3000(数组长度),在我的代码中它将是 2000 + 3000。还有其他想法吗? 您的代码不是 2000 + 3000(即线性),使用.indexOf
只是隐藏了二次性。它还在那里。
但我已经分享了我对这个问题的看法。我已经检查了两个功能时间。我的函数比循环函数运行得更快。
@ArifRathod 那又怎样? 在大 O 方面并不快。它仍然是二次的:持续的因子改进与关于算法复杂性的面试问题无关。让我换一种方式来解决这个问题:如果数组分别是 2000 万个元素和 3000 万个元素,你仍然认为你的答案足够快吗?【参考方案5】:
您可以使用第一个数组(无论它们是否已排序)构建散列,然后迭代第二个数组并检查散列中是否存在!
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);
【讨论】:
【参考方案6】:由于数组是排序的,所以二分查找是关键。
基本上,您正在搜索数组中的项目。
您将项目与数组的中间索引(长度/2)进行比较
如果两者相等,你就找到了。
如果项低于数组中间索引处的项,则将项与索引长度 / 4 -> ((0 + 长度 / 2) / 2) 的索引进行比较,如果低于,则在索引 ( (length / 2) + length) / 2(上半部分的中间)等等。
这样,如果在示例中您必须在 40 000 长度的数组中搜索项目,更糟糕的是,您会发现该项目不在经过 16 次比较的数组中:
我在一个有 40000 个索引的数组中搜索“某物”,我能找到它的最小索引是 0,最大值是 39999。
"something" > arr[20000]
。让我们假设。我知道现在要搜索的最小索引是 20001,最大值是 39999。我现在正在搜索中间的 (20000 + 39999) / 2。
现在,"something" < arr[30000]
,它将搜索范围从索引 20001 限制到 29999。(20000 + 30000) / 2 = 25000。
"something" > arr[25000]
,我要从 25001 搜索到 29999。(25000 + 30000) / 2 = 27500
"something" < arr[27500]
,我要从 25001 搜索到 27499。(25000 + 27500) / 2 = 26250
"something" > arr[26250]
,我必须从 26251 搜索到 27499。(26250 + 27500) / 2 = 26875
"something" < arr[26875]
,我要从 26251 搜索到 26874。(26250 + 26875) / 2 = 26563
等等......当然,你必须四舍五入以避免浮动索引
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);
【讨论】:
如果您发布工作代码,我会很乐意对此表示赞同。 不,二分查找确实有助于在已排序的数组中查找 一个 元素,但不能比较两个已排序的数组。 @Bergi 我知道是对的,但是没有什么能阻止您循环第一个数组并调用二进制搜索函数。我将编辑我的答案。 @Cid 效率仍然很低,不是面试官想要的 @Bergi 此外,您对效率的看法是错误的。这是大小显着不相等的情况的正确答案。constant * log2 x
将很快变得比 constant + x
小得多,因为 x
变得更大。【参考方案7】:
我们可以迭代一个数组并在另一个数组中找到重复项,但是每次找到匹配项时,我们都会移动到匹配的元素 + 1 以进行嵌套循环中的下一次迭代。它之所以有效,是因为两个数组都已排序。所以每个匹配的数组都比较短(从左到右)。
我们也可以在第二个数组的元素大于第一个时打破嵌套循环(从右到左更短),因为我们永远找不到匹配项(因为数组是有序,只剩下更大的值),这里和示例在两个 10k 元素的数组中查找重复项大约需要 15 毫秒:
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)
我没有打印 res,因为它有 5000 个元素。
【讨论】:
【参考方案8】:由于两个数组都已排序,因此只需保存最新的匹配索引。然后从这个索引开始你的内部循环。
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;
==================
更新:
正如 Xufox 在 cmets 中提到的,如果 a[i] 低于 b[i] 那么你有中断循环,因为它没有继续循环的意义。
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;
【讨论】:
这项改进可以防止检查b
的太低的项目,但它不会阻止检查太高的项目。应该有if(a[i] < b[i]) break;
,否则最坏情况下的复杂度仍然是O(n²)。
@Xufox 是的,你完全正确。我应该编辑我的代码并添加你的代码吗?
如果你愿意,可以。【参考方案9】:
最佳策略是将比较次数和数组读数降至最低。
理论上你想要的是交替你正在处理的列表,以避免不必要的比较。鉴于列表已排序,我们知道列表中任何索引左侧的数字都不能小于当前索引。
假设以下列表A = [1,5]
、列表B = [1,1,3,4,5,6]
和索引a
和b
都从0
开始,您会希望您的代码如下所示:
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)
下面是我的 JavaScript 执行上述算法:
//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);
【讨论】:
【参考方案10】:您可以通过检查每个数组的索引来使用嵌套方法,并通过增加索引来查找值。如果找到相等的值,则增加两个索引。
时间复杂度:最大。 O(n+m),其中n是数组a
的长度,m是数组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;
【讨论】:
只有当每个元素都是唯一的时候,这才像一个魅力 @Cid,如果在同一个数组中有重复,则需要添加另一个while循环,直到相同的值消失。 @MBo 对于大小显着不相等的情况,二分搜索将超过此答案的效率。随着x
变大,constant * log2 x
将很快变得比constant + x
小得多。
@MBo 我不确定你的意思。例如,2000 * log2 40000 ≈ 30000。 2000 * log2 400000 ≈ 37000。这有多奇葩?
@גלעד ברקן 啊哈,现在我确实抓住了。我不小心想到了相反的情况(在小列表中搜索长列表元素)。所以值得根据大小比例选择方法。以上是关于用于在两个数组中查找公共元素的 Javascript 程序的主要内容,如果未能解决你的问题,请参考以下文章