从两个数组中区分额外的元素?
Posted
技术标签:
【中文标题】从两个数组中区分额外的元素?【英文标题】:Distinguishing extra element from two arrays? 【发布时间】:2010-12-08 02:31:13 【问题描述】:我的一个朋友在一次采访中被问到这个问题 -
您已经给出了两个整数数组,每个数组的大小为 10。 两者都包含 9 个相等的元素(比如 1 到 9) 只有一个元素不同。你将如何找到不同的元素?您可以采取哪些不同的方法?
一种简单但冗长的方法是 - 对两个数组进行排序,继续比较每个元素,进行错误比较,你会得到你的结果。
那么有什么不同的方法呢?在面试中指定逻辑作为其预期。不期望特定语言的特定代码。伪代码就足够了。
(请为每个答案提交一种方法)
我问这个问题的目的是,当数组很小的时候还可以。但是当数组大小增加时,你必须想一个非常有效的 n 更快的方法。在这种情况下使用比较是不可取的。
【问题讨论】:
所以我们必须返回不同元素的pair:一个来自第一个数组,一个来自第二个? 您确定问题不是“给定一个数组中的 10 个整数和另一个数组中的 9 个常见整数,您将如何找到唯一的整数?”这是一个更好的面试问题。 你已经很好地重新构建了这个问题。但我的问题仍然是一样的。 即使没有比较是不可能的,我们仍然可以想到最好的方法。 我添加了标签“算法”并删除了“数组”,因为它是“数据结构”的一个子集,也许“逻辑”也可以代替“数组”。但是这个问题可能更多是关于算法而不是数据结构(在我看来),所以至少算法人员应该有机会在赏金结束之前查看它。 【参考方案1】:如果您需要扩展它,那么我会使用世界上众多 Set 实现之一。比如Java的HashSet。
抛出 Set 中的所有第一个数组。然后,对于第二个数组的每个成员,如果它包含在 Set 中,则将其删除;否则将其标记为唯一#2。在此过程之后,该集合的最后一个剩余成员是唯一的 #1。
我可能会这样做,即使是在面试中,甚至对于简单的十元素数组也是如此。人生苦短,当有一扇非常好的门时,不能花时间去寻找爬墙的聪明方法。
【讨论】:
+1,我会再给一个 +1 给那些没有不受欢迎的比较 @Ravi required 的人。我很好奇是否有可能在没有比较的情况下做到这一点...... 奥斯汀,这不是我的答案之一(尽管是伪代码,不是特定于 java 的):***.com/questions/1590405/… 不需要删除任何东西。将第一个数组放入一个集合中,然后开始添加第二个数组。增加集合长度的第一个值是您的唯一值。它是有效的,但它假设存在一个集合结构。假设 unique 是第二个数组中的第一个元素并简单地返回它是相同的。【参考方案2】:这是一种数学方法,灵感来自 Kevin 的答案及其 cmets。
我们将数组命名为A
和B
,并将它们的唯一元素分别设为a
和b
。首先,取两个数组的总和,然后从另一个中减去一个;因为其他一切都取消了,sum(A) - sum(B) = a - b = s
。然后,将两个数组的元素相乘,然后除以另一个。同样,事情取消了,所以mult(A) / mult(B) = a / b = r
。现在,从这些中,我们得到a = rb
,所以rb - b = s
或b = s / (r - 1)
,然后是a = rs / (r - 1)
。
我之所以称之为数学,是因为在实际程序中将事物相乘可能不是一件合理的事情。关键是要有两个不同的操作,它们都分别允许取消行为,并且一个分布在另一个之上。后一个属性在从rb - b = s
到b = s / (r - 1)
时使用,这对于我第一次尝试的加法和 XOR 来说是行不通的。
【讨论】:
不能用 XOR 和减法解决。 XOR 最终会给出加法 a+b ,减法会给出 a-b 吗?如果我错了,请纠正我。【参考方案3】:这可以通过两个序列的sum和平方和快速解决。并且计算这些总和肯定会比建议的哈希更快,并且不涉及序列项之间的任何比较。
操作方法如下:如果这两个集合是 ai 和 bi ,则称A和B为和,A2和B2为平方和,即A2 = Sum(ai2),为方便起见,D=AB 和 D2=A2-B2。因此,D=ab 和 D2=a2-b2,其中 a和b是不同的两个元素,由此我们看到
a = (D2+D2)/(2*D) b = a - D
这是因为,根据代数,a2-b2=(a+b)(ab) 或 D2=(a+b)D,所以a+b=D2/D,又因为我们也知道ab,所以可以求出a 和 b。
Python中的例子可能更有说服力
a, b = 5, 22 # the initial unmatched terms
x, y = range(15), range(15)
y[a] = b
print "x =", x # x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
print "y =", y # y = [0, 1, 2, 3, 4, 22, 6, 7, 8, 9, 10, 11, 12, 13, 14]
D = sum(x) - sum(y)
D2 = sum([i**2 for i in x]) - sum([i**2 for i in y]) #element-wise squaring
a = (D2+D*D)/(2*D)
b = a - D
print "a=%i, b=%i" % (a, b)
#prints a=5, b=22 which is correct
(当然,这与 jk 的答案有些相似,只是它不需要所有项的乘法以及会产生的巨大数字,但感谢 jk 提出了数学方法的想法。)
【讨论】:
+1 无论数组是否排序,它都可以工作,并且需要线性计算时间【参考方案4】:从技术上讲,您可以在恒定时间内完成,因为数组(以及其中的值)是有限的。对于泛化问题,我们必须弄清楚一些更棘手的问题。
这是一个线性时间解决方案。
首先,我们应该基于一个数组构建一个散列。哈希表中的值查找需要 O(1 + k/n) 次比较 [1]
,其中 k 是哈希表的长度。因此迭代第一个数组(包含 n 个元素)并查找每个值需要 O(n+k)。
然后我们迭代另一个,查找散列中的每个元素。当没有找到一个元素时——这是另一个数组中唯一的一个。 (再次O(n + k))。然后我们迭代 hash 来寻找第二个唯一元素 (O(k))。
总时间为 O(n+k)。因为让 k 大于 n 没有意义,所以它是线性解。
Perl 代码:
sub unique
my ($arr, $brr) = @_;
my %hash = map$_ => 1 @$arr;
%hash$_-- for @$brr;
return grep $_ keys %hash;
【讨论】:
【参考方案5】:在 LINQ 中:
var unique1 = (from a in arrayA where !arrayB.Contains(a) select a).First();
var unique2 = (from b in arrayB where !arrayA.Contains(b) select b).First();
return new Pair(unique1, unique2);
...
public sealed class Pair<T0, T1>
public T0 Item1 get;set;
public T1 Item2 get;set;
public Pair(T0 item1, T1 item2)
Item1 = item1;
Item2 = item2;
//plus GetHashCode, equality etc.
【讨论】:
【参考方案6】:这是另一种可能性。与我的previous answer 不同,它不会修改传入的数组,并且应该具有较低的 big-O 界限(O(n) 而不是 O(n^2) - 假设恒定时间哈希表查找),但会占用大量时间更多内存。
function findUnique(a:Array, b:Array):Array
var aHash:Hashtable = buildHash(a);
var bHash:Hashtable = buildHash(b);
var uniqueFromA:int;
var uniqueFromB:int;
for each(value:int in a)
if(!bHash.contains(value))
uniqueFromA = value;
break;
else
/* Not necessary, but will speed up the 2nd for-loop by removing
* values we know are duplicates. */
bHash.remove(value);
for each(value:int in b)
if(!aHash.contains(value))
uniqueFromB = value;
break;
return [uniqueFromA, uniqueFromB];
function buildHash(a:Array):Hashtable
var h:Hashtable = new Hashtable();
for each(value:int in a)
h[value] = true;
return h;
【讨论】:
【参考方案7】: 您已经给出了两个大小为 10 的整数数组。 两者都包含 9 个相等的元素(比如 1 到 9) 只有一个元素不同。根据约束条件,您可以在线性时间内非常快速地解决这个问题。如果你持有一个 int[10],那么你可以假设索引 1 处的元素对应于数字 1;元素本身包含两个数组的计数。下面的伪代码可以快速解决问题:
let buckets = new int[10] // init all buckets to zero
for num in arr1 do buckets[num]++ // add numbers from the first array
for num in arr2 do buckets[num]++ // add from the second array
for i in 1 to 9 do // find odd man out
if buckets[i] <= 1 then return i
这本质上是一个有界哈希表。这仅在我们给定的元素列表被限制在 1 到 9 之间时才有效。
从技术上讲,您甚至不需要保留每个元素的运行计数。原则上,您可以简单地遍历 arr1,然后遍历 arr2,直到遇到未从第一个数组散列的元素。
【讨论】:
您只返回一个值。有 2 个唯一值,每个数组中都有一个。而且我认为值可以是任何值,而不仅仅是 1 到 9。 @Herms:看起来我们对 OP 有相互矛盾的解释。当我可以说“不要责怪编码器,责怪需求”时,我喜欢它;)【参考方案8】:之前几乎所有答案背后的逻辑总是相同的:使用数学中的集合运算来解决问题。
数学中的集合只能包含每个元素一次。所以下面的列表不可能是数学意义上的集合,因为它包含一个数字 (3) 两次:
1, 2, 3, 4, 3, 5
由于集合操作,特别是检查一个元素是否已经存在于集合中,是常见的操作,大多数语言都有数据结构可以有效地实现这些集合操作。所以我们可以在我们的解决方案中简单地回退到这个:
// Construct set from first list:
Set uniques = Set.from(list1);
// Iterate over second list, check each item’s existence in set.
for each (item in list2)
if (not uniques.Contains(item))
return item;
集合的不同实现产生不同的性能,但这种性能总是优于简单的解决方案(对于大型列表)。特别是,存在两种实现:
作为(自平衡)search tree。树的元素已排序,因此通过使用二分搜索(或其变体)查找特定元素是有效的。查找因此具有 O(log n) 的性能。从输入创建树集的性能为 O(n · log n)。这也是整体表现。 Hash tables 可以实现为具有 O(1) 的平均查找性能(并且通过一些技巧,这也可以成为最坏情况的性能)。创建哈希表可以在 O(n) 中完成。因此,哈希表可以实现 O(n) 的整体运行时间。 Bloom filters 提供了一个很好的概率解决方案 - 即解决方案可能实际上是错误的,但我们可以控制(不)发生这种情况的可能性。它特别有趣,因为它非常节省空间。 ……还有许多其他实现。在每种情况下,用法都保持不变,上面的伪代码为您的问题提供了教科书式的解决方案。 Java 实现可能如下所示:
// Construct set from first list:
Set<Integer> uniques = new HashSet<Integer>(list1);
// Iterate over second list, check each item’s existence in set.
for (int item : list2)
if (! uniques.Contains(item))
return item;
请注意,这看起来几乎与伪代码一模一样。 C#、C++ 或其他语言的解决方案不会有太大的不同。
EDIT 糟糕,我刚刚注意到请求的返回值是不匹配元素的 pair。但是,这个要求不会改变推理,几乎不会改变伪代码(做同样的事情,使用互换列表)。
【讨论】:
如果您的散列函数以某种方式参数化,布隆过滤器不必引导您找到错误的解决方案。如果您使用第一个数组来构建过滤器,然后测试第二个数组的元素的成员资格,那么可能发生的最糟糕的事情是不同的元素会在成员资格检查中触发误报。因此,当您完成第二组时,您会意识到您无法确认任何元素的非成员身份。现在您可以使用不同的哈希函数重新初始化并重复整个过程,直到您可以确认非成员身份。【参考方案9】:这可以使用 Xor 来完成。
首先异或两个数组中的所有元素。让 x 和 y 成为每个数组的额外元素。 剩下的是 x^y。
现在在异或中,如果设置了一个位,则意味着它被设置在两个数字之一中,而不是另一个。
我们可以使用它来查找丢失的个人号码。所以找到一个在 a^b 中设置的位。获得最右边的位很容易。可以通过
n& ~(n-1)
(1110) & ~(1101) = 0010
为了得到每个单独的数字,我们将两个数组的数字分成两部分,数字有检查位设置,哪些没有。我们对每个集合进行异或,所以我们得到值 a 和 b。这会抵消掉所有重复的元素,并分离出 x 和 y。
这可能会让人很困惑。现在取 x=3, y = 2
x=110
y=010
x^y=100
所以当我们设置位时,我们得到的数字是 bitset = 100。第三位被设置。假设数组元素为 5,1(都重复两次)
5=101
6=001
现在,5 有第三位,所以我们用 x 异或它
我们得到 x^5^5 = x
同样,6 没有设置第 3 位,因此与 y 异或。
我们得到 y^1^1 = y
代码
for(i=0;i<n;i++)
xor = xor^a[i]^b[i];
set_bit = xor & ~(xor-1) ;
for(i = 0; i < n; i++)
if(a[i] & set_bit_no)
x = x ^ a[i]; /*XOR of first set */
else
y = y ^ a[i]; /*XOR of second set*/
if(b[i] & set_bit_no)
x = x ^ b[i]; /*XOR of first set */
else
y = y ^ b[i]; /*XOR of second set*/
这类似于这里发布的方法 http://www.geeksforgeeks.org/find-two-non-repeating-elements-in-an-array-of-repeating-elements/
【讨论】:
这是真正的最佳解决方案,速度快且没有内存开销。基于哈希映射/集合的解决方案实在是太慢了。【参考方案10】:根据您使用的语言,它可能有一个内置的方法来处理数组差异。 php 做http://ca3.php.net/array_diff
【讨论】:
那是你所说的内置功能。我期待一种方法(逻辑) 使用“内置函数”必须算数,不是吗?在我看来,目标是用任何语言制定解决方案,否则没有太多方法可以避免您的语言提供的功能,除非您正在编写机器代码。 Code Duck——在一些面试中,潜在雇主会测试原始的 CS 问题解决能力,让你做这件事,就好像你没有方便的功能或语言结构来使它更容易。这是一个纯粹算法问题的测试,而不是“你能解决它”的问题。 FWIW。【参考方案11】:这里有一些简单的伪代码来解决问题。我假设修改数组是可以的,并且数组有一个 remove(value) 方法(或者你可以简单地写一个)。它接受 2 个数组并返回一个包含 2 个值的数组,第一个是第一个数组的唯一值,第二个是第二个数组的唯一值。
function findUnique(a:Array, b:Array):Array
var uniqueFromA:int;
var uniqueFromB:int;
for each(value:int in a)
var len:int = b.length;
b.remove(value);
/* b's length didn't change, so nothing was removed, so the value doesn't
* exist in it. */
if(b.length == len)
uniqueFromA = value;
/* Only the unique value in b still exists in b */
uniqueFromB = b[0];
return [uniqueFromA, uniqueFromB];
【讨论】:
【参考方案12】:如果不进行比较,就不可能解决这个问题。一些现代语言确实有设置差异和其他聚合运算符,但它们通过内部进行比较来工作。如果总数组大小是奇数(这里不是这种情况),那么它确实可以将元素 xor 在一起。我认为 ALU 的 xor 运算是否应该被视为比较的问题是值得商榷的。
如果不指定语言,就无法引用库,因此唯一可能的解决方案是基于比较的伪代码说明。
所以你去:
a <- first element
a_count = 1
b_count = 0
loop over remaining elements
if element != a
b <- element
++b_count
else
++a_count
if found_result
break loop
end loop
found_result
if a_count > 1 and b_count > 0
the unique one is b
return true
if b_count > 1 and a_count > 0 # a_acount actually always > 0
the unique one is a
return true
return false
【讨论】:
【参考方案13】:将第一个数组的每个元素放入一个哈希中。对于第二个数组中的每个项目,如果它已经在哈希中,则删除它,否则添加它。最后,您有一个带有两个键的哈希,它们是您的唯一元素。
Ruby 中的简单版本
def unique(a,b)
h=
a.each do |ax|
h[ax]=true
end
b.each do |bx|
if h[bx]
h.delete(bx)
else
h[bx]=true
end
end
h.keys
end
有了更多的 Ruby 个性,但仍然在我们不能简单地做 (a | b) - (a & b)
或 a.to_set ^ b.to_set
的宇宙中:
def unique(a,b)
h =
a.each do |ax|
h[ax] = true
end
b.each do |bx|
h.delete(bx) or h[bx] = true
end
h.keys
end
【讨论】:
【参考方案14】:分别对数组 a、b 进行集合 A、B。 A \ B 为您提供 A 中的附加整数。B \ A 为您提供 B 中的附加整数。
如果这些操作中的任何一个返回一个空集,那么额外的整数会在数组中出现两次。您在构造集合时会发现:向集合添加重复整数不会增加集合的大小。
【讨论】:
【参考方案15】:如何获取每个数组的总和并从另一个数组中减去一个?不同之处在于额外的元素。
例如:A = 1,2,3,4,5 B = 1,2,3,4
总和(A)= 15,总和(B)= 10; 15 - 10 = 5 这是额外的元素。
【讨论】:
【参考方案16】:这是一个不需要散列或集合、O(1) 空间和 O(n) 时间的解决方案。它不是发布的最佳解决方案,但它是一个很好的解决方案。您所做的就是将 list1 中的值相加,然后将 list2 中的值相加,然后找出差异。
javascript
var findExtra= function (l1, l2)
var l1_sum = l1.reduce(function(prev, curr)
return prev + curr;
);
var l2_sum = l2.reduce(function(prev, curr)
return prev + curr;
);
console.log(l1_sum, l2_sum);
if (l1.length < l2.length) return l2_sum - l1_sum;
return l1_sum - l2_sum;
console.log(findExtra([1,2,3,4,-1], [1,2,3,4]));
【讨论】:
【参考方案17】:两次通过数组就足够了。
第一遍:将较短列表中的每个项目添加到哈希图(python 中的 dict)。 第二遍:对于较长列表中的每个项目,检查哈希图中是否存在键(O(1)搜索时间)。如果不是,则该键是唯一条目。
总时间复杂度:O(2n) = O(n)
【讨论】:
【参考方案18】:给定两个数组,大小为“n”的 A1 和大小为“n-1”的 A2,这两个数组具有相同的 除了我们必须找到的元素。
注意:A1 中的元素可以重复。
例子:
A1:2,5,5,3
A2:2,5,3
输出:5
A1:1,2,3,3,3
A2:2,3,1,3
输出:3
public static void main(String args[])
int[] a =1,2,3,3,3;
int[] b =2,3,1,3;
int flag=1;
int num=0;
List<Integer> lst = new ArrayList<>(b.length);
for(int i : b)
lst.add(Integer.valueOf(i));
for(int i=0;i<a.length;i++)
flag=1;
for(int j=0;j<lst.size();j++)
if(a[i] == lst.get(j))
lst.remove(j);
flag=0;
break;
if(flag == 1)
num=a[i];
System.out.println(num);
【讨论】:
【参考方案19】: int[] a = 1, 2, 3, 5;
int[] b = 1, 2, 3, 5, 6;
HashSet set = new HashSet();
for (int o : b)
set.add(o);
for (int p : a)
if (set.contains(p))
set.remove(p);
Iterator iterator = set.iterator();
while (iterator.hasNext())
Log.d("TAG", " " + iterator.next());
【讨论】:
以上是关于从两个数组中区分额外的元素?的主要内容,如果未能解决你的问题,请参考以下文章