【问题标题】:Find duplicate in array with a memory efficient approach使用内存有效的方法在数组中查找重复项
【发布时间】:2019-02-04 06:48:42
【问题描述】:

A 是一个整数数组。

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

意思是0 <= A[i] <= A.Length-1

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

例如:

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

然后

result = 2

这是一道面试题。我使用另一个数组来存储项目并检查它何时重复。然后它给了我一些测试用例的超时时间。 面试官建议只对数组进行一次循环,不要创建任何额外的数据结构。

【问题讨论】:

  • 我建议使用HashSet(但那是使用额外的变量)。当Add 返回false 你得到了答案。它是否足够高效 - 如果不查看示例输入和基准代码就不确定。
  • 我还问如果我创建一个哈希表会怎样,他拒绝了。我想这与价值范围有关。
  • 您可以将输入本身用作哈希集。每次看到一个值时,将 A.Length 添加到与该索引对应的项目。不需要其他数据结构。如果你发现一个已经 > A.length.. 你有一个重复。跟踪他们
  • 看看这个:geeksforgeeks.org/…
  • 尝试以下操作:int[] a = { 3, 4, 2, 5, 2, 3 }; int[] b = a.Select((x, i) => new { number = x, index = i }).GroupBy(x => x.number).Select(x => x.Min(y => y.index)).ToArray();

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


【解决方案1】:

我想改进@AryanFirouzian 的解决方案并使用yield return 返回所有重复项。此外,使用临时变量可以简化代码。

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;
        }
    }
}

但是,此解决方案不会返回索引较低的元素,如果有超过 2 个相同的副本,那么它将多次返回相同的值。另一个问题是不能将 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

我们的元素是没有标识的整数。 IE。我们可以在任何索引处返回其中一个重复项,因为无法区分两个相等的整数。如果元素具有标识(它们可能是具有相同值但引用不同的引用类型,或者具有不涉及相等性测试的其他字段),我们将不得不返回第一个匹配项

yield return (firstIndex, Math.Abs(A[firstIndex]) % A.Length);

满足所有要求。

【讨论】:

    【解决方案2】:

    注意:如果存在值为零的元素,则解决方案将失败。 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])];
        }
    

    【讨论】:

    • 是的,负数或 +A.length 或多或少是相同的想法
    • 是的,同样的想法。但我感谢您花时间清楚地解释它。
    • 为什么要把所有的索引都移一个?如果A[i] 为零怎么办?你会得到一个 OOB 异常,除非项目是 FP,否则 A[x] == -A[x]。即使你有一个负零,-0 &lt; 0 == false
    • 这是一个错误,感谢您指出。我之前正在研究 Matlab 项目,这是一种基于单的索引语言:D 混合起来:D 不错。
    • 你仍然有零值元素的问题。我不太相信它可以被淘汰。
    【解决方案3】:

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

    每次看到一个值时,将 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] 范围内,是否所有项目都是无符号的,或者我可以根据需要使用负数吗? - 等等。

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

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

    【讨论】:

      【解决方案4】:

      对于想要解决问题的人,我建议使用两种变体(在 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]));
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2021-08-16
        • 2018-06-12
        • 2022-08-14
        • 2015-08-14
        • 2011-03-02
        • 1970-01-01
        • 2021-08-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多