javascript计算数组交集,并集,网上有很多,有些书也有介绍, 很多都是写一个set类,再添加一些方法,本质上都是利用了多次循环
我在写这个的时候,也是被别人问到,才想到,有没有一种方法,可以用一次循环就解决求交集,并集的办法,当然,我这里说的一次循环,是不含重新排序的循环,和去重复的循环,具体如何实现,后面有代码说明
先列出条件:
1 有两个不知道长度数组都是按递增排列,需要返回两数组并集,并且没重复项且递增排序(数组内,可以有重复项)
数组形式如下:
var arr = [1, 5, 10, 15, 20, 23, 51,52,52]
var brr = [2, 14, 15, 21, 26, 29,32,32,44,46,48]
下面分析一下,如何求并集
(写普通文本,a[1]1]写不上,就写到代码片段里了)
(1)需要对两个数组,各安排一个指针,假设,数组为a,b两个,
如果a[1]1],只需要调整a数组的指针位置,让a[2]和a[1]对比,
在此过程,涵盖去重,把小的一方的值push进新数组
(2)两个数组,由于不知道长度,只知道都是递增排列,也不知道,哪一个数组会率先被遍历完
例如:
(a) a=[1,2,3,7],b=[2,3,4,5,6]
这种情况,b.length虽然长,但b肯定先被遍历完,因为当a到7的时候,b的值都比a的小,所以肯定b的指针会一直调整,直到走完
(b) a=[1,2,3,4],b=[2,3,4,5,6]
这种情况,a的指针肯定先走完
(3)综上,所以根本无法判断哪个数组会率先走完,所以再判断中,要再次判定两个数组length,若任意一方走完,就可退出循环,并拼接上,另一个数组的剩余项,如果不确定剩余项是否有重复,只对要拼接的剩余项,进行下去重,就可以了
经过分析,首先需要一个数组去重方法,
var repeat = function (arr) { //数组去重
var newarr = [], obj = {}, len = arr.length;
for (var i = 0; i < len; i++) {
var temp = arr[i]
if (!obj.hasOwnProperty(temp)) {
newarr.push(temp)
obj[temp] = temp;
}
}
return newarr
}
接下来,需要真正的数组求并集方法
附上几个图,有助于理解指针,画的很糙T-T
1 .指针都在初始位置
2.a[i]小于b[j]时,指改变a的指针
3 当a[i]大于等于b[j]时,a指针不变,只改变b的指针
var reset = function (a, b) {
//利用toString给数组拍照,判断是否相等,若相等,直接去重返回
if (a.toString() === b.toString()) return repeat(a);
//获得数组长度,从两个数组中,最长单位为终值;
var aLen = a.length - 1,bLen=b.length-1;//获取两个数组实际数组个数
var len = aLen > bLen ? aLen + 1 : bLen + 1;//规定数组长度,已两个数组中,最长单位为终值;
//设定新数组用来接收唯一值,obj用来判定是否有重复项,去重,设flag判断最后是哪个数组先走到头
//初始化新数组,对象用来去重判断,flag默认false,代表b数组先走完
var newarr = [], obj = {}, flag = 'b-end';
//i对应a数组指针,j对应b数组指针
for (var i = 0, j = 0; i < len; i++ , j++) {
if (a[i] < b[j]) {
//确保唯一性,防止放入重复项
if (!obj.hasOwnProperty(a[i])) {
//判断当前是否超过最后一位,越过最后一位,值会为undefined,所以没越位才放入数值
if (a[i] !== undefined) {
obj[a[i]] = a[i];
newarr.push(a[i])
}
}
//因为a[i]
//求并集必须要判断数组是否已走完,手动退出循环,要不然,会形成死循环,因为当一个数组走到最后越位的时候,会undefinde和其他值去比,不会报错,永远在循环里++或者--,可以自己调试看一下
//只需要判断i是否达到a数组元素最终个数,若想等,即可跳出循环
if (i == aLen) {
flag = 'a-end';//改变flag状态为a数组走完,跳出循环
break;
}
j--;//如果a[i]小于等于b[i]时候,对j进行--,循环进入下一次,j++,两者抵消,保证数字大的位置指针不变化
}
else {
if (!obj.hasOwnProperty(b[j])) {
if (b[j] !== undefined) {
obj[b[j]] = b[j];
newarr.push(b[j])
}
}
//因为a[i]>=b[i]时,会push数组b的值,如果有一方数组先走完,这时候肯定是b数组已经走完
//只需要判断j是否达到b数组元素最终个数,若相等,即可跳出循环
if (j == bLen) break;
i--;//如果a[i]>b[i]时候,对i进行--,循环进入下一次,i++,两者抵消,保证数字大的位置指针不变化
}
}
//判断哪个数组先循环完毕出来,直接拼接未走完数组的剩余部分
//但,如果未走完的数组,剩余项有重复的,还是需要对剩余想进行去重后再拼接
if (flag === 'a-end') {
//如果是a数组先走完,b数组可能有剩余项,这时候是第一个a[i]
//特别说明:上面代码中,我把i--或者j--放到分支语句的最后一行,就是防止break退出循环之间i和j没有变化,如果i--或者j--放到break之前的话,这里就应该变成slice(j+1)或者slice(i+1)了
return newarr.concat(repeat(b.slice(j)))
}else return newarr.concat(repeat(a.slice(i)))
}
求交集
普遍求交集,都是嵌套循环去找相同项,但也可以利用一层循环,降低时间复杂度,
原理也是利用指针性质,代码如下,就不在写注释了,理解起来比较简单
var arr1 = [2, 3, 4, 5,7,8,8,8,8];
var arr2 = [3, 4, 5,7,8,3,4,5];
var share = function (a, b) {
a = a.sort((a, b) => a - b);
b = b.sort((a, b) => a - b);
var newarr = [], obj = {},
len = a.length > b.length ? a.length : b.length;
for (var i = 0, j = 0; i < len; i++ , j++){
if (a[i] == b[j]) {
if (!obj.hasOwnProperty(a[i])) {
obj[a[i]] = a[i]
newarr.push(a[i]);
}
//写这个判断目的是减少循环次数,当有一个数组先走完,说明交集已经求完,没必要再进行后面的循环了,也防止有其他不明原因进入死循环,不明白的可自己调试
if (i == len - 1 || j == len - 1) break;
} else {
if (i == len - 1 || j == len - 1) break;
if (a[i] > b[j]) i--
else j--
}
}
return newarr
}
有看到的同学,如果还有更好的方法,可以一起交流,谢谢