如何在经过改组的连续整数数组中找到重复的元素?


72

我最近在某个地方遇到了一个问题:

假设您有一个1001个整数的数组。整数按随机顺序排列,但是您知道每个整数都在1到1000(含)之间。此外,每个数字在数组中仅出现一次,但一个数字出现两次。假设您只能访问一次数组的每个元素。描述找到重复数字的算法。如果在算法中使用了辅助存储,是否可以找到不需要它的算法?

我有兴趣知道的是第二部分,即不使用辅助存储。你有什么主意吗?


13
可以肯定,这已经被问过了,但是找不到确切的qn。按顺序排列的n个整数的总和和重复的整数x将为x + n(n-1)/ 2。
Pete Kirkham'4

您能否将问题标题更改为更具描述性的内容?也许“查找重复的数组元素有特殊的限制”
米哈尔皮亚斯科夫斯基

2
稍有不同的问题有相同的答案:stackoverflow.com/questions/35185/...
starblue


Answers:


103

只需将它们加起来,然后减去如果只使用1001个数字,便可以得到的总数。

例如:

Input: 1,2,3,2,4 => 12
Expected: 1,2,3,4 => 10

Input - Expected => 2

2
@Brian Rasmussen:额外的存储空间在哪里?
leppie

3
@leppie:要保留计算出的总和,但是老实说,我不确切知道OP额外存储意味着什么。无论如何,我喜欢你的回答。
Brian Rasmussen'4

4
@Brian,面试官可能的意思是“不要使用哈希表或数组” ...我很确定O(1)存储(尤其是单个变量)会令人满意。
Michael Aaron Safyan'4

6
该方法工作得很好。但示例应该是类似(1,3,2,4,2 => 12)-(1 + 2 + 3 + 4 => 10)= 2
SysAdmin 2010年

5
@Franci Penov:我不确定面试问题是否应该解决:)
Brian Rasmussen 2010年

77

更新2:有人认为使用XOR查找重复的号码是一个hack或trick俩。我的官方答复是:“我不是在寻找重复的数字,而是在比特集的数组中寻找重复的模式。XOR绝对比ADD更适合操纵比特集”。:-)

更新:这是我上床前的一种好玩的解决方案,它是一种“单行”替代解决方案,需要零附加存储(甚至没有循环计数器),仅接触每个数组元素一次,无损且完全不缩放: -)

请注意,编译器实际上将在编译时计算该表达式的后半部分,因此“算法”将恰好在1002个操作中执行。

而且,如果在编译时也知道数组元素的值,则编译器会将整个语句优化为一个常量。:-)

原始解决方案:即使可以找到正确的答案,也不能满足问题的严格要求。它使用一个额外的整数来保持循环计数器,并访问每个数组元素3次-两次读取并在当前迭代中将其写入,一次读取并在下一次迭代中使用。

好吧,当您遍历数组时,至少需要一个附加变量(或CPU寄存器)来存储当前元素的索引。

除了这一点之外,这是一种破坏性算法,可以安全地扩展到任意N,最高可达MAX_INT。

我将通过一个简单的提示:-)来弄清楚为什么它对您有效。


2
一种非破坏性的方法是将一个蓄能器保持在侧面...我认为也将使其更具可读性。
Matthieu M.

2
@Matthiey M.-但非破坏性解决方案将需要额外的存储,因此违反了问题的要求。
Franci Penov

1
@Dennis Zickefoose-我并不是说带有附加整数变量的非破坏性解决方案不会更好。:-)但是确实违反了问题要求,这就是为什么我选择破坏性算法。至于循环计数器-无法避免这种情况,它是隐式允许的,因为问题表明代码允许一次在数组上迭代一次,没有循环计数器是不可能的。
Franci Penov

1
@Pavel Shved-XOR没有窍门,它是具有众所周知属性(如加法,乘法和其他运算)的数学运算。
Franci Penov

1
@Pavel-同样,您和我以不同的方式看待问题-因为我不是在搜索重复的数字,而是在标志集中搜索重复的模式。当您以这种方式陈述问题时,现在使用加法已成为“肮脏的把戏” :-)
Franci Penov 2010年

22

Franci Penov提出的解决方案的非破坏性版本。

这可以通过使用XOR运算符来完成。

假设我们有一个size数组54, 3, 1, 2, 2
位于索引处:                        0, 1, 2, 3, 4

现在执行XOR所有元素和所有索引中的一个。我们得到2,这是重复元素。这是因为0在XORing中不起作用。其余n-1索引与n-1数组中的相同元素配对,而数组中唯一未配对的元素将是重复项。

该解决方案的最大特点是,它不会遭受基于加法解决方案中出现的溢出问题。

由于这是一个采访问题,因此最好从基于加法的解决方案开始,确定溢出限制,然后给出XOR基于解决方案的解决方案:)

这将使用附加变量,因此不能完全满足问题中的要求。


2
坦白说,我没有得到这些基于XOR的解决方案。基本上,我们试图将“索引”与元素的值进行匹配。如果匹配,则结果将为零;对于重复值,异或结果将为非零。对于一个简单的数组-> {1,2,2},我们将xor 1(元素值)^ 1(索引)^ 0(先前的xor结果)-> 0; 2 ^ 2 ^ 0-> 0; 3 ^ 2 ^ 0->1。这里1是根据XOR解的最终结果值。除非我遗漏了非常明显的内容,否则我看不出这是有效的答案。
Prabhjot'4

@codaddict我认为循环应该从我初始化为1开始。–
Raman Singh

1
@codaddict +1可以清楚地说明并提及溢出(也是非破坏性的)。即使整数具有偏移量,也可以进行一些更改,例如{ 1043, 1042, 1044, 1042 }通过XOR-ing与{ 0, 1042, 1043, 1044 }
legends2k 2014年


7

解释弗朗西斯·佩诺夫的解决方案。

(通常)问题是:给定一个任意长度的整数数组,该数组仅包含重复了偶数次的元素,但一个值重复了奇数次,则找出该值。

解决方案是:

acc = 0
for i in array: acc = acc ^ i

您当前的问题是适应。诀窍是要找到重复两次的元素,因此您需要调整解决方案以弥补这一怪癖。

acc = 0
for i in len(array): acc = acc ^ i ^ array[i]

最终,弗朗西斯的解决方案做到了这一点,尽管它破坏了整个数组(顺便说一句,它只能破坏第一个或最后一个元素...)

但是由于索引需要额外的存储空间,因此如果您还使用额外的整数,我认为您会被原谅的。限制很可能是因为它们希望阻止您使用数组。

如果他们有所需的O(1)空间,这将被更准确地表述(因为这里是任意的,因此可以将其视为N可以是1000)。


我根据你的答案发布的Python的一行stackoverflow.com/questions/2605766/...
JFS

5

加所有数字。整数1..1000的和为(1000 * 1001)/ 2。与您得到的区别是您的电话号码。


3

如果您知道我们的确切数字是1-1000,则可以将结果相加并从总数中减去500500sum(1, 1000))。这将给出重复的数字,因为sum(array) = sum(1, 1000) + repeated number


3

Python中的一线解决方案

arr = [1,3,2,4,2]
print reduce(lambda acc, (i, x): acc ^ i ^ x, enumerate(arr), 0)
# -> 2

@Matthieu M.的答案中说明了其工作原理。


+1,做得好:即使不是代码高尔夫,使用python的内置循环也会更快:)
Matthieu M.

2

好吧,有一种非常简单的方法可以执行此操作... 1到1000之间的每个数字只发生一次,除了重复的数字....因此,从1 .... 1000开始的总和为500500。因此,算法为:

总和= 0
对于数组的每个元素:
   sum + =数组的那个元素
number_that_occurred_twice =总和-500500

1
n = 1000
s = sum(GivenList)
r = str(n/2)
duplicate = int( r + r ) - s

1
public static void main(String[] args) {
    int start = 1;
    int end = 10;
    int arr[] = {1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10};
    System.out.println(findDuplicate(arr, start, end));
}

static int findDuplicate(int arr[], int start, int end) {

    int sumAll = 0;
    for(int i = start; i <= end; i++) {
        sumAll += i;
    }
    System.out.println(sumAll);
    int sumArrElem = 0;
    for(int e : arr) {
        sumArrElem += e;
    }
    System.out.println(sumArrElem);
    return sumArrElem - sumAll;
}

1

没有额外的存储要求(除了循环变量)。


您假设数组已排序。错误的假设。
leppie

3
@leppie:怎么会?我什么也没假设 实际上,它会像其他答案一样使用任何额外的空间。
N 1.1

虽然我的前提确实有问题。它需要额外的两个整数。
Dennis Zickefoose 2010年

@Dennis:井循环变量必须存在,并length使其具有通用性。
N N

@nvl:即使像这样的学术练习,保存单个变量的破坏性算法也不是特别有益。
Dennis Zickefoose 2010年

1

参数和调用堆栈是否算作辅助存储?


编辑:尾声版本


这需要线性堆栈空间,因此肯定是作弊行为。
丹尼斯·齐克福斯

1
抛出另一个参数,您可以对其进行尾调用优化。
cobbal 2010年

1
public int duplicateNumber(int[] A) {
    int count = 0;
    for(int k = 0; k < A.Length; k++)
        count += A[k];
    return count - (A.Length * (A.Length - 1) >> 1);
}

0

三角数T(n)是从1到n的n个自然数的总和。它可以表示为n(n + 1)/ 2。因此,知道在给定的1001个自然数中,只有一个数是重复的,您可以轻松地将所有给定的数相加并减去T(1000)。结果将包含此重复项。

对于三角数T(n),如果n是10的幂,那么还有一种漂亮的方法可以基于10为底的表示找到该T(n):

n = 1000
s = sum(GivenList)
r = str(n/2)
duplicate = int( r + r ) - s

0

我支持所有元素的加法,然后从中减去所有索引的总和,但是如果元素数量很大,这将不起作用。即它将导致整数溢出!因此,我设计了此算法,该算法可能会在很大程度上减少整数溢出的机会。

   for i=0 to n-1
        begin:  
              diff = a[i]-i;
              dup = dup + diff;
        end
   // where dup is the duplicate element..

但是通过这种方法,我将无法找出存在重复元素的索引!

为此,我需要再次遍历数组,这是不可取的。


简单的总和实际上将起作用。如果计算和的变量是无符号的,则整数溢出不是问题。
格雷格·伍兹

0

基于对连续值进行异或运算的性质来改进Fraci答案:

哪里:

或在伪代码/数学lang f(n)中定义为(优化):

典范形式f(n)为:


0

我对问题2的回答:

从1查找数的和与积- (至)N,比方说SUMPROD

从1-N- x -y中找到数字的总和与乘积(假设x,y缺失),例如mySum,myProd

从而:

SUM = mySum + x + y;
PROD = myProd* x*y;

从而:

x*y = PROD/myProd; x+y = SUM - mySum;

如果求解此方程,我们可以找到x,y。


0

在aux版本中,首先将所有值设置为-1,然后在迭代时检查是否已将值插入到aux数组中。如果不是(值必须为-1),则插入。如果有重复,这是您的解决方案!

在没有aux的那个中,您从列表中检索一个元素,然后检查列表的其余部分是否包含该值。如果包含,则在这里找到了。

private static int findDuplicated(int[] array) {
    if (array == null || array.length < 2) {
        System.out.println("invalid");
        return -1;
    }
    int[] checker = new int[array.length];
    Arrays.fill(checker, -1);
    for (int i = 0; i < array.length; i++) {
        int value = array[i];
        int checked = checker[value];
        if (checked == -1) {
            checker[value] = value;
        } else {
            return value;
        }
    }
    return -1;
}

private static int findDuplicatedWithoutAux(int[] array) {
    if (array == null || array.length < 2) {
        System.out.println("invalid");
        return -1;
    }
    for (int i = 0; i < array.length; i++) {
        int value = array[i];
        for (int j = i + 1; j < array.length; j++) {
            int toCompare = array[j];
            if (value == toCompare) {
                return array[i];
            }
        }
    }
    return -1;
}
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.