一个在两个数组中不同的元素。如何有效地找到它?


22

我正在准备进行编码面试,但我真的想不出解决此问题的最有效方法。

假设我们有两个数组,这些数组由未排序的数字组成。数组2包含一个数字,数组1没有。两个数组都有随机定位的数字,不一定以相同的顺序或相同的索引。例如:

阵列1 [78,11,143,84,77,1,26,35 .... n]

数组2 [11,84,35,25,77,78,26,143 ... 21 ... n + 1]

找到不同数字的最快算法是什么?它的运行时间是多少?在此示例中,我们要查找的数字是21。

我的想法是遍历数组1并从数组2中删除该值。迭代直到完成。这应该在O(nlogn)运行时间左右,对吗?


@Jandvorak谢谢你们的回答。我迟到了,刚发完贴后就睡着了。该数组未排序,并且所有项目均出现在两个数组中的随机索引处。
Konstantino Sparakis

@KonstantinoSparakis:此澄清假设两个数组在相同位置包含元素的答案无效。
Mario Cervera


@Paparazzi只是在寻找我在meta软件工程中阅读的解决方案,而该解决方案是在哪里获得的,但是当时我对CS论坛一无所知。我已通知国防部,对其进行清理。
Konstantino Sparakis

@Paparazzi是否有支持它的meta帖子?我个人认为没有任何办法可以很好地执行该政策。
djechlin '16

Answers:


30

我看到了在不同的运行时间下解决此问题的四种主要方法:

  • 解决方案:这就是您建议的解决方案。请注意,由于数组未排序,因此删除需要线性时间。您执行 n次删除;因此,该算法需要二次时间。O(n2)n

  • 解决方案:预先对数组进行排序;然后,执行线性搜索以识别不同的元素。在此解决方案中,运行时间主要由排序操作决定,因此 O nO(nlogn)上限。O(nlogn)

当您确定问题的解决方案时,您应该始终问自己:我能做得更好吗?在这种情况下,您可以巧妙地使用数据结构。请注意,您要做的只是迭代一个数组,并在另一个数组中执行重复查找。哪种数据结构允许您在(预期的)恒定时间内进行查找?您猜对了:哈希表

  • 解决方案(预期):迭代第一个数组并将元素存储在哈希表中;然后,在第二个数组中执行线性扫描,在哈希表中查找每个元素。返回在哈希表中找不到的元素。该线性时间解决方案适用于您可以传递给哈希函数的任何类型的元素(例如,对于字符串数组,其工作原理类似)。O(n)

如果您需要上限保证并且数组严格由整数组成,则最佳解决方案可能是Tobi Alafin建议的解决方案(即使此解决方案不会为您提供第二个数组中不同元素的索引) :

  • 解决方案(保证):总结第一个数组的元素。然后,总结第二个数组的元素。最后,执行减法。注意,由于按位XOR运算符的存在,该解决方案实际上可以推广到其值可以表示为固定长度的位字符串的任何数据类型。Ilmari Karonen的答案对此作了详尽的解释。 O(n)

最后,另一种可能性(在与整数数组相同的假设下)将使用线性时间排序算法,例如计数排序。这将减少基于排序的解决方案从 O n O(nlogn)O(n)


4
但是,如果数字足够大,求和就不是线性的。
Sarge Borsch,2016年

9
关于求和算法的一件好事是它可以与任何阿贝尔群一起使用,而不仅仅是整数(最值得注意的是,uint64cc @sarge)。
John Dvorak

6
@Abdul的事情是,如果您的整数很大,您将无法再假装它们需要相加。我认为,如果考虑到这一点,那么复杂度就会增加到O n ln n 。使用XOR代替普通的加法可以解决这个问题,同时仍然允许任意大的输入数。O(n)O(nlnn)
John Dvorak

2
@JanDvorak不,不是。您假设在abelian组上定义的操作花费固定时间。那不能只是假设。
UTF-8

2
@ UTF-8我不认为那样。但这是在有限组(uint64)中进行的,并且就地位数字加法(加法)在就地操作数的大小上是线性的。因此,在这样的基团的计算的总和线性时间中操作数的总大小。Znd
约翰·德沃夏克

16

差的和数提出溶液托比马里奥实际上可以推广到任何其他数据类型,我们可以定义一个(恒定时间)二进制运算是:Θ(n)

  • ,使得对于任何值bb被定义和相同类型的(或至少它的某些适当超型中,操作者对于其仍然定义);abab
  • 缔合,使得 ;a(bc)=(ab)c
  • 交换,使得 ; 和ab=ba
  • ,使得存在一个逆算子,其满足一个b b = 一个。从技术上讲,只要“减去”两个n个元素之和的耗时不超过On )个时间,该逆运算甚至不必一定是恒定时间。(ab)b=anO(n)

(如果类型只能接受有限数量的不同值,则这些属性足以使它成为Abelian组;即使不是,它也至少将是可交换的可加 半组。)

使用这样的运算,我们可以将数组a = a 1a 2a n的“和”定义为a=(a1,a2,,an)

(a)=a1a2an.
给定另一个阵列包含所有的元件一个一个额外的元件加上X,我们因此具有b=(b1,b2,,bn,bn+1)ax,所以我们可以通过计算发现这一额外的元件: X = (b)=(a)x
x=(b)(a).

例如,如果数组中的值是整数,则可以将整数加法(或对于有限长度整数类型进行模加)用作运算符,并将减法用作逆运算。或者,对于任何其值可以表示为固定长度的位字符串的数据类型,我们可以将按位XOR用作

更一般地说,只要我们有某种方式可逆地删除末尾的填充,我们甚至可以将按位XOR方法应用于可变长度的字符串,方法是根据需要将其填充到相同的长度。

在某些情况下,这是微不足道的。例如,C样式的以null终止的字节字符串隐式地编码了它们自己的长度,因此对它们应用此方法很简单:对两个字符串进行XOR运算时,请用空字节填充较短的字符串以使它们的长度匹配,并修剪所有多余的尾随null最终结果。请注意,中间的XOR-sum字符串可以包含空字节,因此您需要显式存储它们的长度(但是最多只需要其中一个或两个)。

更一般而言,一种适用于任意位串的方法是应用一位填充,其中每个输入位串用单个位填充,然后根据需要匹配任意0位以匹配(填充)长度。最长的输入字符串。(当然,不需要预先明确地执行此填充;我们可以在计算XOR和时仅根据需要应用它。)最后,我们只需要从s中删除任何尾随的0位和最后1位即可。结果。或者,如果我们知道字符串最多为2 321001232字节长,我们可以将每个字符串的长度编码为32位整数并将其添加到字符串之前。或者,我们甚至可以使用一些前缀代码对任意字符串长度进行编码,然后将其添加到字符串之前。也存在其他可能的编码。

实际上,由于根据定义,可以在计算机上表示的任何数据类型都可以表示为有限长度的位字符串,因此该方法可得出该问题的通用解。Θ(n)

唯一可能棘手的部分是,要使取消生效,我们需要为每个值选择一个唯一的规范位串表示形式,如果可以给定两个数组中的输入值,则可能会很困难(实际上,甚至可能无法计算)。以不同的等效表示形式。但是,这不是此方法的特定弱点;如果允许输入包含等效性不确定的值,则解决此问题的任何其他方法也可能失败。


哇,这很有趣。谢谢@IlmariKaronen
Konstantino Sparakis 16/12/17

14

我会将其发布为对Tobi答案的评论,但是我还没有声誉。

作为计算每个列表之和的一种替代方法(特别是如果它们是大列表或包含非常大的数字,它们在求和时可能会溢出数据类型),可以使用xor代替。

只需计算每个列表的异或和(即x [0] ^ x [1] ^ x [2] ... x [n]),然后对这两个值进行异或。这将为您提供无关项目的值(而不是索引)。

这仍然是O(n),并避免了任何溢出问题。


3
我也将使用XOR,因为它看起来更整洁,但说句公道,只要您在实现中使用的语言通过包装支持溢出,溢出实际上就不是问题。
Martin Ender

14

元素= Sum(Array2)-Sum(Array1)

衷心怀疑这是最佳算法。但这是解决问题的另一种方法,也是解决问题的最简单方法。希望能帮助到你。

如果添加的元素数量超过一个,则将无法使用。

对于最佳,最差和平均情况,我的答案具有相同的运行时复杂度,

编辑
经过一番思考,我认为我的答案是您的解决方案。

对于长度的数组,原始操作数找到总和= ñ - 1 求和阵列的1 = ñ - 1点的操作。阵列的总和2 = Ñ + 1 - 1 =nn11=n12=n+11=n

2n121=1的操作。

2n1+1=2n

Θ(n)

编辑:
由于数据类型的某些问题,reffu建议的XOR总和 将更合适


请注意,如果您的值是浮点数,则此方法可能无法产生准确的答案,因为将这些数字相加可能会导致舍入误差。但是,只要a)您的整数类型在溢出时具有明确定义的环绕行为,或b)将总和存储在足够宽的类型的变量中而不会溢出的条件下,它将对整数值有效。
Ilmari Karonen

Ruby的“ BigNum”类可能会处理此问题。
Tobi Alafin '16

如果您的数组包含例如字符串或几乎所有无法有意义地添加的东西,那绝对是行不通的。
gnasher729

是的,我意识到。使用“ XOR”怎么样?它可以用于花车吗?
Tobi Alafin '16

是的,还有指针,以及通常由固定位数组成的任何东西。许多语言都不支持,但这不是根本问题。模加/减将在相同情况下起作用。
哈罗德

1

假设通过取数组1并在随机位置插入元素来创建数组2,或者通过取数组2并删除随机元素来创建数组1。

如果保证所有数组元素都不同,则时间为O(ln n)。您在位置n / 2处比较元素。如果它们相等,则多余的元素从n / 2 +1到数组的末尾,否则从0到n / 2。等等。

如果不能保证数组元素是不同的:您可能是数组1中数字1的n倍,并且数组2中任何位置插入了数字2。在这种情况下,您不看数字就无法知道数字2在哪里。数组元素。因此O(n)。

PS。由于要求已更改,请检查您的库中是否有可用的。在macOS / iOS上,您创建一个NSCountedSet,从数组2中添加所有数字,从数组1中删除所有数字,剩下的是数组2中的所有内容,而不是数组1中的所有内容,而不必依赖于声称还有一个额外的项目。


这个答案是即时的,但是问题已经用新的要求进行了编辑,使您的假设无效。
Mario Cervera

您的新答案似乎正确。时间的复杂度是多少。
Tobi Alafin '16

好吧,首先是编写代码需要多少时间。这很琐碎。NSCountedSet使用散列,因此时间复杂度“通常是线性的”。
gnasher729

-1

var最短,最长;

将最短的时间转换为地图以进行快速引用,并循环最长的时间,直到当前值不在地图中。

在javascript中是这样的:

如果(arr1.length> arr2.length){最短= arr2; 最长= arr1; } else {最短= arr1; 最长= arr2; }

var map = shortest.reduce(function(obj,value){obj [value] = true; return obj;},{});

var差= longest.find(function(value){return !!! map [value];});


没有解释的代码在这里不能算是一个很好的答案。还有你为什么要用!!! ?
Evil

-1

时间复杂度为O(N)的空间复杂度为O(1)

问题陈述:假设array2包含array1的所有元素以及array1中不存在的其他元素。

解决方案是:我们使用xor查找array1中不存在的元素,因此步骤如下:1.从array1开始并对所有元素进行xor并将其存储在变量中。2.取array2并使用存储array1的xor的变量对所有元素进行xor。3.完成该操作后,我们的变量将包含仅在array2中存在的元素。上面的算法之所以起作用是因为xor的以下属性“ a xor a = 0”“ a xor 0 = a”,希望这可以解决您的问题。另外上述建议的解决方案也很好

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.