使用内存有效的方法在阵列中查找重复项

时间:2018-08-29 13:02:13

标签: c# arrays algorithm out-of-memory memory-efficient

A是一个整数数组。

所有值都在0A.Length-1之间

它表示0 <= A[i] <= A.Length-1

我应该找到重复的元素;并且如果有多个重复元素,则选择重复项索引较低的元素。

例如:

a = [3, 4, 2, 5, 2, 3]

然后

result = 2

这是一个面试问题。我使用了另一个数组来存储项目并检查它何时重复。然后,它给了我一些测试用例的超时时间。 采访者建议仅在数组上循环一次,并且不要创建任何其他数据结构。

4 个答案:

答案 0 :(得分:22)

无需其他数据结构。您可以将输入本身用作哈希集。

每次看到一个值时,将A.Length添加到与该索引对应的项目。由于值可能已经增加,因此应将值视为A[i] mod A.length

如果找到的项目已经> = A.length ..,则重复一次。 (请记住,问题表明所有项目都在[0, A.Length-1]区间内)

跟踪重复发现的最低索引。

这导致O(N)复杂度(单遍),并且不使用其他数据结构,即大小O(1)

此方法背后的关键概念是哈希集以这种方式工作。从概念上讲,这与信鸽原理间接相关。 https://en.wikipedia.org/wiki/Pigeonhole_principle

注意:在面试过程中,重要的是提出实施方面的特定问题,讨论限制和假设等: -列表中项目的数据类型是什么? -如果值在[0..A.length-1]范围内,是否所有项目都未签名,或者如果需要可以使用负数吗? -等等

在面试中,我不会说这是一个完美的答案,相反,我会与面试官讨论这些假设并做出相应调整。例如,另一个答案建议使用负数,但项目的数据类型可能是无符号类型,等等。

面试应该触发技术讨论,以探索您的知识和创造力。

答案 1 :(得分:6)

注意:如果元素的值为零,则解决方案将失败。 Olivier的解决方案可以处理此类情况。

使索引为A [i]的元素为负。它只循环一次。

for(int i=0; i<A.Length; i++)
    {
        if (A[Math.Abs(A[i])] < 0){ return Math.Abs(A[i]);}
        A[Math.Abs(A[i])] = -A[Math.Abs(A[i])];
    }

答案 2 :(得分:3)

我想完善@AryanFirouzian的解决方案,并使用yield return返回所有重复项。另外,使用temp变量可以简化代码。

public static IEnumerable<int> FindDuplicates(int[] A)
{
    for (int i = 0; i < A.Length; i++) {
        int absAi = Math.Abs(A[i]);
        if (A[absAi] < 0) {
            yield return absAi;
        } else {
            A[absAi] *= -1;
        }
    }
}

但是,此解决方案不返回索引较低的元素,如果存在两个以上相同的副本,则它将多次返回相同的值。另一个问题是不能将0设为负数。

更好的解决方案可以消除重复的结果,但仍返回第二个索引,并且值有0。它还返回索引本身以证明错误的索引问题

public static IEnumerable<(int index, int value)> FindDuplicates(int[] A)
{
    for (int i = 0; i < A.Length; i++) {
        int x = A[i] % A.Length;
        if (A[x] / A.Length == 1) {
            yield return (i, x);
        }
        A[x] += A.Length;
    }
}

经过测试

var A = new int[] { 3, 4, 2, 5, 2, 3, 3 };
foreach (var item in FindDuplicates(A)) {
    Console.WriteLine($"[{item.index}] = {item.value}");
}

返回

[4] = 2
[5] = 3

我的最终解决方案消除了所有这些问题(至少我希望如此):它通过将(i + 1) * A.Length添加到值的第一次出现来对第一个索引本身进行编码。 (i + 1)是因为i可以是0。然后可以使用反向操作(A[x] / A.Length) - 1对索引进行解码。

然后,因为我们只想返回第一个重复值,所以我们将该值设置为负值以将其排除在进一步处理之外。随后,可以使用Math.Abs(A[i]) % A.Length检索原始值。

public static IEnumerable<(int index, int value)> FindDuplicates(int[] A)
{
    for (int i = 0; i < A.Length; i++) {
        int x = Math.Abs(A[i]) % A.Length;
        if (A[x] >= 0) {
            if (A[x] < A.Length) { // First occurrence.
                A[x] += (i + 1) * A.Length; // Encode the first index.
            } else { // Second occurrence.
                int firstIndex = (A[x] / A.Length) - 1; // Decode the first index.
                yield return (firstIndex, x);

                // Mark the value as handeled by making it negative;
                A[x] *= -1; // A[x] is always >= A.Length, so no zero problem.
            }
        }
    }
}

返回预期结果

[2] = 2
[0] = 3

我们的元素是没有身份的整数。即由于无法区分两个相等的int,因此我们可以在任何索引处返回重复项之一。如果元素具有标识(它们可以是具有相等值但引用不同的引用类型,或者具有不包含在相等性测试中的其他字段),则我们必须使用

返回第一次出现
yield return (firstIndex, Math.Abs(A[firstIndex]) % A.Length);

满足所有要求。

答案 3 :(得分:2)

对于想要实现该问题的人,我建议使用两种变体(在c#中与标签相同),一种使用接受的答案,一种使用另一种答案的方式,使用元素的反面。但是,最后一个解决方案的值为零存在问题,需要一些技巧。

第一个解决方案

using System;
public class Program
{
    public static void Main()
    {
        int[] a = {3, 4, 0, 5, 2, 3};
        int N = 6;
        int min_index = 0; 
        bool found = false;
        int index = -1;
        int i = 0;
        while(i < N && !found)
        {

            if(a[i] >= N) 
                index = a[i] % N;
            else
                index = a[i];

            if(a[index] >= N) //its a duplicated elements 
            {
                min_index = i;
                found = true;
            }else
            {
                a[index] += N;
            }
            i++;

        }

        Console.WriteLine("Result = " + a[min_index] % N);
    }
}

第二个解决方案

    using System;
public class Program
{
    public static void Main()
    {
        int[] a = {3, 4, 2, 5, 2, 3};
        int N = 6;
        int min_index = N-1; 
        bool found = false;
        int index = -1;
        int i = 0;
        while(i < N && !found)
        {
            if(a[i] == -N+1) //it was 0
                index = 0;
            else
                index = Math.Abs(a[i]);

            if(a[index] < 0 || a[index] == -N+1) //its a duplicated elements 
            {
                min_index = i;
                found = true;
            }else
            {
                if(a[index] > 0)
                {
                    a[index] = -a[index];
                }else
                {
                    a[index] += -N+1;
                }
            }
            i++;
        }

        if(a[min_index] == -N+1)
            a[min_index] = 0;

        Console.WriteLine("Result = " + Math.Abs(a[min_index]));
    }
}