【问题标题】:Finding missing elements in an array查找数组中缺失的元素
【发布时间】:2011-07-12 02:55:31
【问题描述】:

假设您有一个大小为 n 的数组 A[1..n],它包含来自集合 {1..n} 的元素。但是,缺少两个元素(并且可能重复了两个数组元素)。找到缺失的元素。

例如,如果 n=5,A 可能是 A[5] = {1,2,1,3,2};所以缺少的元素是 {4,5}

我使用的方法是:

int flag[n] = {0};  
int i;  
for(i = 0; i < n; i++)  {  
  flag[A[i]-1] = 1;  
 }  

for(i = 0; i < n; i++)  {  
 if(!flag[i]) {  
    printf("missing: %d", (i+1));  
}  

空间复杂度为 O(n)。我觉得这是一个非常幼稚和低效的代码。那么您能否提供一个更好的算法,具有更好的空间和时间复杂度。

【问题讨论】:

  • 您不能在O(n) 时间内完成它,因为您必须至少查看每个元素一次。但是,可能会有一些使用较少内存的解决方案。
  • flag[A[i]] = 1; 是禁忌!根据您的帖子,每个 A[i] 的值都在 1 和 n 之间; flag[n] 未定义(标志的索引从 0n-1)。哦......你正在尝试访问A[0],根据你的帖子,不应该考虑;并且您无法访问A[n](您看到的最后一个AA[n-1])。 OTOH,如果 A 被定义为 A[5] = {1, 2, 1, 3, 2} 描述中存在不一致
  • 哎呀对不起..虽然你应该明白它应该是 flag[A[i]-1] = 1;谢谢会修改它..

标签: c algorithm


【解决方案1】:

一个示例代码 sn-p 用于查找缺失元素而不对下面的数组进行排序:

     public static void series(int[] arr) {
     for (int i = 0; i < arr.length; i++) {
        while (arr[i] != i + 1) {
            int jump = arr[arr[i] - 1];
            if (jump == arr[i]) {
                break;
            }
            arr[arr[i] - 1] = arr[i];
            arr[i] = jump;
        }
     }
     System.out.println("Missing number is ");
     for (int i = 0; i < arr.length; i++) {
        if (arr[i] != i + 1) {
            System.out.println(i + 1);
        } else {
            arr[i] = -1;
        }
     }

此代码适用于从 0 到 N 的一系列数字。

【讨论】:

  • 我认为我们可以将while循环条件while ( arr[i] != i + 1 )替换为while ( arr[arr[i] - 1] != arr[i] ),然后去掉if (jump == arr[i])条件,直接做swap ( arr[arr[i] - 1], arr[i] )
  • @Chandan Hegde 是的,可以减少代码,但我们没有在这里定义交换函数。所以为了避免混淆,我使用了额外的变量'jump'。
【解决方案2】:

As you have given an array of n size and find the missing number when it's in a sequence.

#include<stdio.h>
main()
{
print("value of n");
scan("%d",&n);
print("enter the elements");
for(i=0;i<n;i++)
scan("%d",&a[i]);
for(i=0;i<n;i++)
{
d1[i]=a[i+1]-a[i];
temp=d1[i];
d[i]=temp;
}
for(i=0;i<n;i++)
{
if(d[i]==d[i+1]
{
c=d[i];
break;
}
}
for(i=0;i<n;i++)
b[i]=a[0]+i*c;
for(i=0;i<n;i++)
{
miss=0;
for(j=0;j<n;j++)
{
if(b[i]!=a[j])
{
miss++;
}
if(miss==n)
print("missing no. %d",b[i]);
}
}

It would find the missing when its in sequence only.

【讨论】:

    【解决方案3】:

    我们知道我们正在寻找 1 到 N 之间的元素创建一个包含 1 到 N 的哈希集。

    foreach(int i in input)
    {
       if(hashset.contains(i))
       {
          hashset.delete(i);
       }
    }
    
    return "remaining elements in Hashset.";
    

    Hashset 中剩下的元素就是缺失的元素。

    【讨论】:

      【解决方案4】:

      正如@j_random_hacker 所指出的,这与Finding duplicates in O(n) time and O(1) space 非常相似,并且对我的答案的改编也适用于此处。在伪代码中:

      for i := 1 to n
          while A[A[i]] != A[i] 
              swap(A[i], A[A[i]])
          end if
      end for
      
      for i := 1 to n
          if A[i] != i then 
              print i
          end if
      end for
      

      第一个循环对数组进行置换,以便如果元素 x 至少出现一次,则其中一个条目将位于位置 A[x]

      请注意,尽管它有一个嵌套循环,但它仍然在 O(N) 时间运行 - 仅当存在 i 使得 A[i] != i 时才会发生交换,并且每个交换设置至少一个元素使得 @987654329 @,以前不是这样的。这意味着交换的总数(以及 while 循环体的总执行次数)最多为 N-1

      【讨论】:

      • 由于我们跳过第 0 个索引并迭代到 i=N,看来我们总是需要将给定的 arr 扩展 1 并将第一个 ele 复制到新索引。尝试了一个缺少一个重复案例,例如[4,3,5,2,2] 以及两个缺失两个重复的情况,例如[5,3,5,2,2]。还尝试了一个失踪案例,例如[4,3,5,2] 根据您对this 问题的回答,这似乎也需要 k+1 个额外的空间,而不仅仅是 k。还是我哪里出错了?
      • @dd9chndn:这是使用基于 1 的数组的伪代码(因为这就是问题的表述方式)。
      【解决方案5】:

      理论上,

      即使使用只读数组,也可以在 O(1) 空间(在 RAM 模型中,即 O(1) 个字)和 O(n) 时间内完成。

      警告:长篇文章包含一些数学知识。如果您只对代码感兴趣而不是算法/证明,请跳到代码部分。不过,您需要阅读算法部分的某些部分才能理解代码。


      算法

      假设缺失的数字是 x 和 y。

      数组有两种可能:

      1) 一个数字重复3次,数组中剩余的数字恰好出现一次。

      对于这种情况,分桶异或技巧将起作用。

      对数组的所有元素与 1,2,...,n 进行异或运算。

      你最终得到 z = x XOR y。

      z 至少有一位非零。

      现在根据该位(两个桶)区分数组的元素,再次通过数组进行异或。

      你最终会得到 x 和 y。

      获得 x 和 y 后,您可以确认这些是否确实是缺少的元素。

      如果碰巧确认步骤失败,那么我们必须有第二种情况:

      2) 两个元素恰好重复两次,其余元素只出现一次。

      让两个重复的元素分别为 a 和 b(x 和 y 是缺失的元素)。

      警告:数学超前。

      S_k = 1^k + 2^k + .. + n^k

      例如S_1 = n(n+1)/2S_2 = n(n+1)(2n+1)/6

      现在我们计算 7 个东西:

      T_1 = Sum of the elements of the array = S_1 + a + b - x - y.
      T_2 = Sum of the squares = S_2 + a^2 + b^2 - x^2 - y^2
      T_3 = Sum of cubes = S_3 + a^3 + b^3 - x^3 - y^3
      T_4 = Sum of fourth powers = S_4 + a^4 + b^4 - x^4 - y^4
      ...
      T_7 = Sum of seventh powers = S_7 + a^7 + b^7 - x^7 - y^7
      

      注意,我们可以使用 O(1) 个单词(整数为 1)来处理溢出问题。 (我估计 8-10 个字就够了)。

      Ci = T_i - S_i

      现在假设 a,b,x,y 是 4 次多项式 P(z) = z^4 + pz^3 + qz^2 + rz + s 的根

      现在我们在p,q,r,s中尝试将以上七个方程转化为四个线性方程。

      例如,如果我们这样做4th Eqn + p * 3rd Eqn + q* 2nd equation + r* 1st equation

      我们得到

      C4 + p*C3 + q*C2 + r*C1 = 0

      同样我们得到

      C5 + p*C4 + q*C3 + r*C2 + s*C1 = 0
      C6 + p*C5 + q*C4 + r*C3 + s*C2 = 0
      C7 + p*C6 + q*C5 + r*C4 + s*C3 = 0
      

      这是p,q,r,s 中的四个线性方程,可以通过高斯消元等线性代数技术求解。

      请注意,p,q,r,s 将是有理数,因此只能使用整数算术计算。

      现在假设我们得到了上述方程组的解p,q,r,s

      考虑P(z) = z^4 + pz^3 + qz^2 + rz + s

      上面的方程式基本上是在说什么

      P(a) + P(b) - P(x) - P(y) = 0
      aP(a) + bP(b) - xP(x) -yP(y) = 0
      a^2 P(a) + b^2 P(b) - x^2 P(x) - y^2 P(y)  = 0
      a^3 P(a) + b^3 P(b) - x^3 P(x) - y^3 P(y) = 0
      

      现在是矩阵

         1   1  -1 -1
         a   b   -x   -y
         a^2 b^2 -x^2 -y^2
         a^3 b^3 -x^3 -y^3
      

      Vandermonde matrix 具有相同的行列式,因此是可逆的,如果a,b,x,y 是不同的。

      因此我们必须拥有P(a) = P(b) = P(x) = P(y) = 0

      现在检查1,2,3,...,n 中的哪一个是x^4 + px^3 + qx^2 + rx + s = 0 的根。

      因此这是一个线性时间常数空间算法。


      代码

      我编写了以下 C# (.Net 4.0) 代码,它似乎适用于我尝试过的几个示例......(注意:我没有费心迎合上面的案例 1)。

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      
      using System.Numerics;
      
      namespace SOManaged
      {
          class Program
          {
              static void Main(string[] args)
              {
                  ulong[] inp = {1,3,2,1,2};
                  ulong[] inp1 = { 1,2,3,4,5,6,7,8,
                                   9,10,11,13,14,15,
                                   16,17,18,19,20,21,5,14};
      
                  int N = 100000;
                  ulong[] inp2 = new ulong[N];
                  for (ulong i = 0; i < (ulong)N; i++)
                  {
                      inp2[i] = i+1;
                  }
                  inp2[122] = 44;
                  inp2[419] = 13;
      
                  FindMissingAndRepeated(inp);
                  FindMissingAndRepeated(inp1);
                  FindMissingAndRepeated(inp2);
              }
      
              static void FindMissingAndRepeated(ulong [] nums)
              {
                  BigInteger[] C = new BigInteger[8];
      
                  // Compute the C_i
                  for (int k = 0; k < 8; k++)
                  {
                      C[k] = 0;
                  }
      
                  BigInteger i = 1;
                  BigInteger n = 0;
      
                  for (int j = 0; j < nums.Length; j++)
                  {
                      n = nums[j];
                      i = j + 1;
                      for (int k = 1; k < 8; k++)
                      {
                          C[k] += i - n;
                          n = n * nums[j];
                          i = i * (j + 1);
                      }
                  }
      
      
                  for (int k = 1; k <= 7; k++)
                  {
                      Console.Write("C[" + k.ToString() + "] = " + 
                                     C[k].ToString() +", ");
                  }
                  Console.WriteLine();
      
                  // Solve for p,q,r,s
                  BigInteger[] pqrs = new BigInteger[4];
                  BigInteger[] constants = new BigInteger[4];
                  BigInteger[,] matrix = new BigInteger[4, 4];
      
                  int start = 4;
                  for (int row = 0; row < 4; row++ )
                  {
                      constants[row] = -C[start];
      
                      int k = start-1;
                      for (int col = 0; col < 4; col++)
                      {
                          matrix[row, col] = C[k];
                          k--;
                      }
      
                      start++;
                  }
      
                  Solve(pqrs, matrix, constants, 4);
      
                  for (int k = 0; k < 4; k++)
                  {
                      Console.Write("pqrs[" + k.ToString() + "] = " 
                                     + pqrs[k].ToString() + ", ");
                  }
                  Console.WriteLine();
      
                  // Find the roots.
                  for (int k = 1; k <= nums.Length; k++)
                  {
                      BigInteger x = new BigInteger(k);
                      BigInteger p_k = x * x * x* x + pqrs[0] * x* x * x 
                                       + pqrs[1] * x * x + pqrs[2] * x 
                                       + pqrs[3];
      
                      if (p_k == 0)
                      {
                          Console.WriteLine("Found: " + k.ToString());
                      }
                  }
              }
      
              // Solve using Cramer's method.
              // matrix * pqrs = constants.
              static void Solve(BigInteger[] pqrs, BigInteger[,] matrix, 
                                BigInteger[] constants, int n)
              {
                  BigInteger determinant = Determinant(matrix, n);
      
                  for (int i = 0; i < n; i++)
                  {
                      BigInteger[,] numerator = Replace(matrix, constants, n, i);
                      BigInteger numDet = Determinant(numerator,4);
                      pqrs[i] = numDet/ determinant;
                  }
              }
      
              // Replace a column of matrix with constants.
              static BigInteger[,] Replace(BigInteger[,] matrix, 
                                 BigInteger[] constants, int n, int col)
              {
                  BigInteger[,] newMatrix = new BigInteger[n, n];
                  for (int i = 0; i < n; i++)
                  {
                      for (int j = 0; j < n; j++)
                      {
                          if (j != col)
                          {
                              newMatrix[i, j] = matrix[i, j];
                          }
                          else
                          {
                              newMatrix[i, j] = constants[i];
                          }
                      }
                  }
      
                  return newMatrix;
              }
      
              // Recursively compute determinant for matrix.
              static BigInteger Determinant(BigInteger[,] matrix, int n)
              {
                  BigInteger determinant = new BigInteger(0);
                  int multiplier = 1;
      
                  if (n == 1)
                  {
                      return matrix[0,0];
                  }
      
                  for (int i = 0; i < n; i++)
                  {
                      BigInteger [,] subMatrix = new BigInteger[n-1,n-1];
                      int row = 0;
                      for (int j=1; j < n; j++)
                      {
                          int col = 0;
                          for (int k = 0; k < n ; k++)
                          {
                              if (k == i)
                              {
                                  continue;
                              }
                              subMatrix[row,col] = matrix[j,k];
                              col++;
                          }
                          row++;
                      }
      
                      BigInteger subDeterminant = Determinant(subMatrix, n - 1);
                      determinant += multiplier * subDeterminant * matrix[0,i];
                      multiplier = -multiplier;
                  }
      
                  return determinant;
              }
          }
      }
      

      输出是

      C[1] = 6, C[2] = 36, C[3] = 180, C[4] = 864, C[5] = 4116, C[6] = 19656, C[7] = 9
      4380,
      pqrs[0] = -12, pqrs[1] = 49, pqrs[2] = -78, pqrs[3] = 40,
      Found: 1
      Found: 2
      Found: 4
      Found: 5
      
      
      C[1] = 15, C[2] = 407, C[3] = 9507, C[4] = 215951, C[5] = 4861515, C[6] = 108820
      727, C[7] = 2424698067,
      pqrs[0] = -53, pqrs[1] = 980, pqrs[2] = -7396, pqrs[3] = 18480,
      Found: 5
      Found: 12
      Found: 14
      Found: 22
      
      
      C[1] = 486, C[2] = 189424, C[3] = 75861486, C[4] = 31342069984, C[5] = 130971109
      69326, C[6] = 5492487308851024, C[7] = 2305818940736419566,
      pqrs[0] = -600, pqrs[1] = 83183, pqrs[2] = -3255216, pqrs[3] = 29549520,
      Found: 13
      Found: 44
      Found: 123
      Found: 420
      

      【讨论】:

      • @Georg:这是一些数据。对于示例问题1,1,2,2,3c1 = 6 C2 = 36 c3 = 180 C4 = 864 C5 = 4116 C6 = 19656 C7 = 94380 求解给出p=-12, q= 49, r = -78, s= 40。对应的多项式是(z-1)(z-2)(z-4)(z-5)
      • 我用了一个计算器和这个网站:wims.unice.fr/wims/wims.cgi我目前没有时间写一个完整的程序,很遗憾。
      • @Georg:我写了一些代码并用它编辑了答案。理想情况下,它应该是相反的方式......需要证明的代码,而不是需要代码的证明! :-)
      • @Moron:无论如何,我很久以前就赞成您的回答。 :) 你对这个答案的奉献给我留下了深刻的印象。
      • @Scott:按照您自己的逻辑,您将如何在 O(1) 空间中维护数组的索引?这就是我指定字 RAM 模型的原因。如果 n 占用 O(1) 个字,那么 n^7 也是如此。 8-10 来自 1^7 + 2^7 + ... + n^7 的顺序为 n^8。
      【解决方案6】:

      您的解决方案还不错。这是另一种选择 - 对列表进行排序并遍历它检查相邻的数字。当有间隙时,打印中间的所有数字。如果 k 是数组的长度,n 是要计数的数,我们得到 O(k lg k + n) 时间,O(1) 空间

      【讨论】:

      • 这取决于排序算法,通常它已经是 O(n),所以可能它的 OP 代码效率更高。
      • @redent84:OPs 代码已经是最优的了,任何排序算法都会让它变慢。
      • O(1) 空间如果输入数组是可变的 - 但是如果输入数组是可变的,我们可以玩一些技巧来使提问者的接近 O(1) 空间也在 O(k) 时间内.
      • 取决于最佳手段,对于低 k 和高 n,这更节省时间,而且总是更节省空间......
      • k 不能小于 n - 您的 k 是问题中的 n,而您的 n 是两个差距中实际较高的数字。所以没有“低k高n”。
      【解决方案7】:

      当已知数组仅包含 1 到 n 之间的木材时,以下是识别所有缺失数字的方法之一,不使用任何额外的空间。时间复杂度为 O(n)。

      让我们取一个最小的数 k,这样它就不应该在数组中,然后 k = n+1(我们称之为添加因子)。

      首先循环遍历每个数组,对于每个 a[i],我们将更新 a[a[i] - 1] += k; 在这个循环之后,每个数组元素都包含两组信息,数组元素中的原始数字 + k *(数组中第 i 个数字的出现次数)。

      在第二个循环中,您可以通过将每个位置的数字除以 k 来找出第 i 个数字的重复次数。第 i 个位置的原始数字是 a[i] % k;

      让我们看一个例子

      A[5] = {1,2,1,3,2};

      这里(加因子)k = 5(数组长度)+ 1 = 6

      在第一个循环数组之后,如果原始元素是 m 并且第 i 个数字的出现次数是 O(i) 结果数组元素将是 m + k * O(i) 这个元素除(整数)由 k 你会得到第 i 个元素的出现, 和 %k 你会得到原始数组。

      A = {1 + 6*2, 2 + 6*2, 1 + 6*1, 3+6*0 , 2+6*0 }
      A = {13, 14, 7, 3, 2 }

      以下是 C# 代码(对不起,我的 C 已经有一段时间生锈了。)只需替换 Printf 和 scanfs 即可移植到任何语言。

          static void Main(string[] args)
          {
              int[] A = { 1, 2, 1, 3, 2 };
              PrintDuplicateAndMissing(A);
              Console.ReadLine();
          }
      
          static void PrintDuplicateAndMissing(int[] array)
          {
              int addfactor = array.Length + 1;
              for (int i = 0; i < array.Length; i++)
              {
                  array[array[i] - 1] += addfactor; // -1 only if array contains from 1 to n. if it is 0 to n (this -1 is not required)
              }
              for (int i = 0; i < array.Length; i++)
              {
                  if ( (array[i] / addfactor) == 0 )
                      Console.WriteLine(string.Format("{0} is missing", i + 1)); // i + 1 only if array is 1 to n, if 0 to n then +1 is not required
                  array[i] %= addfactor; //restore original content of the array
              }
          }
      

      【讨论】:

        【解决方案8】:

        这有点古怪 因为你所有的数字都是积极的(问题)。如果 i 存在于数组中,我会将位置 i-1 处的数字设为负数。

        int i;  
        for(i = 0; i < n; i++)  {  
            A[abs(A[i])-1] = -1*abs(A[abs(A[i])-1]);
         }  
        
        for(i = 0; i < n; i++)  {  
         if(A[i]>0) {  
            printf("missing: %d", i+1);  
        }  
        

        复杂度 O(n),没有辅助数组用户,但是破坏了输入数组。

        【讨论】:

        • 这个算法不起作用。不仅如此,它还会导致数组索引越界(如果幸运的话,还会导致分段违规错误)。
        • 我尝试了您的输入,它给了我正确的答案。你的意思是这不是你的面试官所期望的?
        • 任何不同于从 1 开始到 N 结束的序列的输入都会导致错误。这有点冒险!
        • @redent84 :是的,那是正确的,我只是按照问题给出的 :)
        • @Zimbabao 可以在第二个for循环中重置数组的符号,不会增加复杂度
        【解决方案9】:

        循环每个元素 0...n-1。

        x = abs(A[i]) (with i = 0...n-1);
        
        A[x - 1] can be: 
        > 0: we haven't checked the element, change its sign to negative:
            A[x - 1] = -A[x - 1]
        < 0: we already found the same number
        

        在循环结束时,传递每个 A[0...n-1]。正元素+1的索引为缺失数。

        如果

        y = abs(A[i]) > 0: i + 1 is missing.
        

        在 C# 中

        var arr = new[] { 1, 2, 1, 2, 4 };
        
        for (int i = 0; i < arr.Length; i++) {
            int x = Math.Abs(arr[i]);
            int y = arr[x - 1];
            if (y > 0) {
                arr[x - 1] = -arr[x - 1];
            }
        }
        
        for (int i = 0; i < arr.Length; i++) {
            int x = arr[i];
            if (x > 0) {
                Console.WriteLine("Missing {0}", i + 1);
            } else {
                arr[i] = -arr[i];
            }
        }
        

        而且数组和新的一样好。

        【讨论】:

        • 我认为他错了。我认为他可以访问负索引元素。尝试 2、1、1、2、4
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-07
        • 2018-09-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多