【问题标题】:Algorithm to find the smallest non negative integer that is not in a list查找不在列表中的最小非负整数的算法
【发布时间】:2010-10-22 01:53:25
【问题描述】:

给定一个整数列表,我怎样才能最好地找到列表中不是的整数?

列表可能非常大,整数也可能很大(即 BigIntegers,而不仅仅是 32 位整数)。

如果有什么不同,列表“可能”是排序的,即 99% 的时间它会被排序,但我不能指望总是被排序。

编辑 -

为了澄清,给定列表 {0, 1, 3, 4, 7},可接受解的示例是 -2、2、8 和 10012,但我更愿意找到最小的非负解 (即2)如果有一种算法可以找到它而无需对整个列表进行排序。

【问题讨论】:

  • 您需要不在列表中的最小(正)整数或不在列表中的任何整数吗?
  • 您想找到不在列表中的最小正整数吗?任何指定的整数?哪一个?
  • “可能已排序”可能意味着完全排序将非常快。根据“未排序”和“排序”列表的算法,这可能是值得的。

标签: algorithm sorting integer


【解决方案1】:

一种简单的方法是迭代列表以获得最高值n,然后您知道n+1 不在列表中。

编辑:

找到最小的未使用正数的方法是从零开始并扫描列表以查找该数字,如果找到该数字则重新开始并增加。为了提高效率,并利用列表被排序的高概率,您可以将小于当前的数字移动到列表的未使用部分。

此方法使用列表的开头作为较小数字的存储空间,startIndex 变量跟踪相关数字的开始位置:

public static int GetSmallest(int[] items) {
    int startIndex = 0;
    int result = 0;
    int i = 0;
    while (i < items.Length) {
        if (items[i] == result) {
            result++;
            i = startIndex;
        } else {
            if (items[i] < result) {
                if (i != startIndex) {
                    int temp = items[startIndex];
                    items[startIndex] = items[i];
                    items[i] = temp;
                }
                startIndex++;
            }
            i++;
        }
    }
    return result;
}

我做了一个性能测试,我创建了 100000 个从 0 到 19999 的随机数的列表,这使得平均最低数字约为 150。在测试运行时(每个测试列表有 1000 个),该方法在未排序的列表中找到了最小的数字平均在 8.2 毫秒内,在排序列表中平均在 0.32 毫秒内。

(我没有检查该方法离开列表的状态是什么,因为它可能会交换其中的一些项目。它至少离开包含相同项目的列表,并且我认为它在列表中向下移动较小的值它实际上应该为每次搜索变得更加排序。)

【讨论】:

  • 如果 items = {1, 2, 3},您的解决方案返回 0。但不是要求数字必须为正数吗?
【解决方案2】:

如果数字没有任何限制,则可以进行线性搜索,找到列表中的最大值并返回大一的数字。

如果数字确实有限制(例如 max+1 和 min-1 可能溢出),那么您可以使用works well on partially sorted data 的排序算法。然后遍历列表,找到第一对不连续的数字 v_i 和 v_{i+1}。返回 v_i + 1。

要获得最小的非负整数(基于问题中的编辑),您可以:

  • 使用上面的部分排序对列表进行排序。二进制搜索列表中的 0。从该值遍历列表,直到找到两个数字之间的“间隙”。如果到达列表末尾,则返回最后一个值 + 1。

  • 将值插入哈希表。然后从 0 向上迭代,直到找到不在列表中的整数。

【讨论】:

    【解决方案3】:

    除非已排序,否则您将不得不逐项进行线性搜索,直到找到匹配项或到达列表末尾。如果你能保证它是排序的,你总是可以使用 BinarySearch 的数组方法或者只是滚动你自己的二进制搜索。

    或者像 Jason 提到的那样,总是可以选择使用 Hashtable。

    【讨论】:

      【解决方案4】:

      “可能已排序”意味着您必须将其视为完全未排序。如果您当然可以保证它已排序,这很简单。只需查看第一个或最后一个元素并加或减 1。

      【讨论】:

      • 不正确:有些算法在输入排序后的假设下表现得更好,如果输入最终没有被排序(行为与 QuickSort 相反,它在排序时表现最差)输入)。
      【解决方案5】:

      我的正确性和性能都达到了 100%, 您应该使用 N log(N) 复杂度的快速排序。 给你...

          public int solution(int[] A) {
          if (A != null && A.length > 0) {
              quickSort(A, 0, A.length - 1);
          }
      
          int result = 1;
          if (A.length == 1 && A[0] < 0) {
              return result;
          }
      
          for (int i = 0; i < A.length; i++) {
              if (A[i] <= 0) {
                  continue;
              }
              if (A[i] == result) {
                  result++;
              } else if (A[i] < result) {
                  continue;
              } else if (A[i] > result) {
                  return result;
              }
          }
      
          return result;
      }
      
      private void quickSort(int[] numbers, int low, int high) {
          int i = low, j = high;
          int pivot = numbers[low + (high - low) / 2];
      
          while (i <= j) {
              while (numbers[i] < pivot) {
                  i++;
              }
              while (numbers[j] > pivot) {
                  j--;
              }
      
              if (i <= j) {
                  exchange(numbers, i, j);
                  i++;
                  j--;
              }
          }
          // Recursion
          if (low < j)
              quickSort(numbers, low, j);
          if (i < high)
              quickSort(numbers, i, high);
      }
      
      private void exchange(int[] numbers, int i, int j) {
          int temp = numbers[i];
          numbers[i] = numbers[j];
          numbers[j] = temp;
      }
      

      【讨论】:

      • 这是在 Codility 演示测试中正确性和性能均达到 100% 的唯一解决方案。这应该是公认的答案。
      【解决方案6】:

      理论上,找到最大值并加 1。假设您受到 BigInteger 类型的最大值的约束,如果未排序,则对列表进行排序,并查找间隙。

      【讨论】:

        【解决方案7】:

        您是否在寻找on-line algorithm(因为您说输入任意大)?如果是这样,请查看Odds algorithm

        否则,如前所述,对输入进行散列,搜索并打开/关闭布尔集的元素(集合中的散列索引)。

        【讨论】:

          【解决方案8】:

          有几种方法:

          • 在列表中找到最大的 int 并将其存储在 x 中。 x+1 将不在列表中。这同样适用于使用 min() 和 x-1。

          • 当 N 是列表的大小时,分配一个大小为 (N+31)/32 的 int 数组。对于列表中的每个元素,设置数组索引i/32 处整数的位v&amp;31(其中v 是元素的值)。忽略 i/32 &gt;= array.length 的值。现在搜索第一个数组项 '!= 0xFFFFFFFF'(对于 32 位整数)。

          【讨论】:

            【解决方案9】:

            如果你不能保证它是排序的,那么你有一个 O(N) 的最佳时间效率,因为你必须查看每个元素以确保你的最终选择不存在。那么问题来了:

            1. 可以在 O(N) 内完成吗?
            2. 什么是最佳空间效率?

            Chris Doggett 的求最大值并加 1 的解决方案既 O(N) 又节省空间(O(1) 内存使用)

            如果你只想要最好的答案,那就另当别论了。

            【讨论】:

              【解决方案10】:

              除非您 100% 确定它已排序,否则最快的算法仍然需要至少查看列表中的每个数字一次,以至少验证一个数字是否在列表中。

              【讨论】:

                【解决方案11】:

                假设这是我正在考虑的问题:

                1n 范围内的所有ints 中有一个set,但其中一个ints 缺失。告诉我哪个int 不见了。

                这是一个很容易用一些简单的数学知识解决的问题。已知范围1 .. n 的总和等于n(n+1) / 2。所以,让W = n(n+1) / 2 并让Y = 你集合中数字的总和。您的集合中缺少的整数 X 将是 X = W - Y

                注意: SO需要支持MathML

                如果这不是那个问题,或者如果它更普遍,那么其他解决方案之一可能是正确的。我只是无法从这个问题中真正分辨出来,因为它有点模糊。

                编辑:好吧,自从编辑后,我可以看出我的回答是绝对错误的。有趣的数学,不过如此。

                【讨论】:

                  【解决方案12】:

                  我已经使用 Linq 和二进制搜索解决了这个问题。我得到了全面的100%。这是我的代码:

                  using System.Collections.Generic;
                  using System.Linq;
                  
                  class Solution {
                  
                      public int solution(int[] A) {
                          if (A == null) {
                              return 1;
                          } else {
                              if (A.Length == 0) {
                                  return 1;
                              }
                          }
                          List<int> list_test = new List<int>(A);
                          list_test = list_test.Distinct().ToList();
                          list_test = list_test.Where(i => i > 0).ToList();
                          list_test.Sort();
                          if (list_test.Count == 0) {
                              return 1;
                          }
                          int lastValue = list_test[list_test.Count - 1];
                          if (lastValue <= 0) {
                              return 1;
                          }
                          int firstValue = list_test[0];
                          if (firstValue > 1) {
                              return 1;
                          }
                          return BinarySearchList(list_test);
                      }
                  
                      int BinarySearchList(List<int> list) {
                          int returnable = 0;
                          int tempIndex;
                          int[] boundaries = new int[2] { 0, list.Count - 1 };
                          int testCounter = 0;
                          while (returnable == 0 && testCounter < 2000) {
                              tempIndex = (boundaries[0] + boundaries[1]) / 2;
                              if (tempIndex != boundaries[0]) {
                                  if (list[tempIndex] > tempIndex + 1) {
                                      boundaries[1] = tempIndex;
                                  } else {
                                      boundaries[0] = tempIndex;
                                  }
                              } else {
                                  if (list[tempIndex] > tempIndex + 1) {
                                      returnable = tempIndex + 1;
                                  } else {
                                      returnable = tempIndex + 2;
                                  }
                              }
                              testCounter++;
                          }
                          if (returnable == list[list.Count - 1]) {
                              returnable++;
                          }
                          return returnable;
                      }
                  
                  }
                  

                  Large_2 测试的最长执行时间为 0.08 秒

                  【讨论】:

                  • 我一直想知道那些弹出的答案,指的是获得 100%,而现在您正在谈论一个特定的测试。这个问题是否与一些外部编码测试相关联?
                  • 有一个叫做 Codility 的网站,一些公司用它来为想为他们工作的人做态度测试。该网站有一个非常有用的演示测试,您可以在这里查看:link。 100% 的正确性和准确性,以及我发布的代码都参考了这个演示测试。 @BasselKh 5 年前的回答也提到了这一点。
                  • 啊,我明白了。谢谢。
                  【解决方案13】:

                  您需要对列表进行排序。这意味着要么知道它已排序,要么对其进行排序。

                  • 对列表进行排序。如果已知列表已排序,请跳过此步骤。 O(n lg n)
                  • 删除所有重复的元素。如果元素已经保证是不同的,请跳过此步骤。 O(n)
                  • 使用二分搜索令 B 为 1 在列表中的位置。 O(lg n)
                  • 如果 1 不在列表中,则返回 1。请注意,如果从 1 到 n 的所有元素都在列表中,则 B+n 处的元素必须为 n+1。 O(1)
                  • 现在执行排序二分法搜索,从 min = B,max = 列表末尾开始。调用枢轴P的位置。如果P处的元素大于(P-B+1),则在范围[min,pivot]上递归,否则在范围(pivot,max]上递归。继续直到min=pivot =max O(lg n)
                  • 您的答案是(pivot-1 处的元素)+1,除非您位于列表的末尾并且 (P-B+1) = B 在这种情况下它是最后一个元素 + 1。O(1 )

                  如果列表已经排序并且具有不同的元素,这将非常有效。当列表只有非负元素或列表不包含值 1 时,您可以进行乐观检查以使其更快。

                  【讨论】:

                    【解决方案14】:

                    刚刚接受了一次采访,他们问我这个问题。这个问题的答案可以通过最坏情况分析找到。列表中存在的最小自然数的上限是长度(列表)。这是因为,在给定列表长度的情况下,列表中出现的最小数字的最坏情况是列表 0,1,2,3,4,5....length(list)-1。

                    因此,对于所有列表,列表中不存在的最小数字小于等于列表的长度。因此,用 n=length(list)+1 个零启动一个列表 t。对应于列表中的每个数字 i(小于等于列表的长度)标记将值 1 分配给 t[i]。列表中第一个零的索引是列表中不存在的最小数字。因为,这个列表的下界 n-1,对于至少一个索引 j

                    【讨论】:

                    • 简单地从0到最后一个元素遍历列表。如果找到所有元素,则只需取 n + 1 个元素。有些我不清楚你在第二段中想说什么。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-01-20
                    • 2022-11-23
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-07-14
                    相关资源
                    最近更新 更多