在数字列表中找到一个“洞”


14

找到给定的未排序整数列表中不存在(且大于列表的最小值)的第一个(最小)整数的最快方法是什么?

我原始的方法是对它们进行排序并逐步浏览列表,是否有更好的方法?


6
@Jodrell我认为对无限级进行排序将很困难;-)
maple_shaft

3
@maple_shaft同意,可能需要一段时间。
Jodrell

4
您如何首先为未排序的列表定义?
Jodrell 2012年

1
我只是意识到这可能属于StackOverflow,因为这实际上不是一个概念性问题。
JasonTrue 2012年

2
@JasonTrue在FAQ中,If you have a question about… •algorithm and data structure concepts它是关于恕我直言的话题。
maple_shaft

Answers:


29

假设您说“数字”的意思是“整数”,则可以使用大小为2 ^ n的位向量,其中n是元素数(例如,您的范围包括1到256之间的整数,则可以使用256-位或32字节的位向量)。当您在范围的位置n遇到整数时,请设置第n位。

枚举整数集合后,您可以遍历位向量中的位,寻找设置为0的任何位的位置。它们现在与丢失的整数的位置n相匹配。

这是O(2 * N),因此是O(N),并且可能比对整个列表进行排序更节省内存。


6
好吧,直接比较一下,如果您拥有所有正数的无符号32位整数但均为1,则可以在大约半GB的内存中解决缺失的整数问题。如果您进行排序,则必须使用8 GB以上的内存。而且排序,除了在这种特殊情况下(您的列表在拥有位向量后才进行排序)几乎总是n log n或更糟,因此,除非常数大于成本的复杂性,否则采用线性方法即可。
JasonTrue 2012年

1
如果您不知道先验范围怎么办?
Blrfl 2012年

2
如果您具有整数数据类型Blrfl,则即使您没有足够的信息来进一步缩小范围,也可以确定范围的最大范围。如果您碰巧知道列表很小,但不知道确切大小,则排序可能是一个更简单的解决方案。
JasonTrue 2012年

1
或先在列表中进行另一个循环,以找到最小和最大的元素。然后,您可以分配一个具有最小大小的精确大小的数组作为基本偏移量。仍为O(N)。
确保

1
@JPatrick:不是家庭作业,不是商业,我几年前毕业于CS :)。
Fabian Zeindl 2012年

4

如果首先对整个列表进行排序,则可以保证最坏的运行时间。同样,您选择的排序算法也很关键。

这是解决该问题的方法:

  1. 使用堆排序,重点放在列表中的最小元素上。
  2. 每次交换之后,看看是否有空隙。
  3. 如果发现差距,则return:找到答案。
  4. 如果找不到间隙,请继续交换。

是堆排序可视化


一个问题,您如何确定列表中“最小”的元素?
Jodrell 2012年

4

仅出于神秘和“聪明”,在只有一个“孔”的数组的特殊情况下,您可以尝试基于XOR的解决方案:

  • 确定数组的范围;这是通过为数组的第一个元素设置“ max”和“ min”变量来完成的,此后的每个元素,如果该元素小于min或大于max,则将min或max设置为新价值。
  • 如果范围比集合的基数小1,则只有一个“空”,因此可以使用XOR。
  • 将整数变量X初始化为零。
  • 对于从最小值到最大值(包括最大值)的每个整数,将该值与X进行异或运算并将结果存储在X中。
  • 现在,将数组中的每个整数与X进行XOR,将每个连续的结果像以前一样存储到X中。
  • 完成后,X将是您的“孔”的值。

类似于位向量解决方案,此过程将在大约2N的时间内运行,但对于任何N> sizeof(int),都需要较少的存储空间。但是,如果数组具有多个“孔”,则X将是所有孔的XOR“和”,这将很难或不可能分离为实际的孔值。在那种情况下,您会退回到其他方法,例如其他答案中的“枢轴”或“位向量”方法。

您也可以使用类似于数据透视方法的方法来递归此操作,以进一步降低复杂性。根据枢轴点重新排列阵列(这将是左侧的最大值和右侧的最小值;在枢轴旋转时找到整个数组的最大值和最小值将是微不足道的)。如果枢轴的左侧有一个或多个孔,则仅递归到该侧;否则递归到另一侧。在任何可以确定只有一个孔的点上,都可以使用XOR方法找到它(总的来说,这比继续向下一直旋转到具有已知孔的两个元素的集合要便宜得多,这是该方法的基本情况纯数据透视算法)。


那简直太聪明了!现在,您能否提出一种方法来使用可变数量的孔?:-D

2

您会遇到的数字范围是多少?如果该范围不是很大,则可以通过使用两次扫描(线性时间O(n))来解决此问题,该扫描使用具有与数量一样多的元素的数组进行时间交换。您可以再扫描一次即可动态找到范围。为了减少空间,您可以为每个数字分配1位,这样每个字节可以存储8个数字。

如果在扫描遍历中找到的分钟数比最近找到的分钟数少1,则其他选择可能更适合早期情况,并且就位而不是复制内存,这是将选择排序修改为尽早退出。


1

不,不是。由于任何尚未扫描的数字始终可以是填充给定“漏洞”的数字,因此您无法避免至少扫描一次每个数字,然后将其与可能的邻居进行比较。您可能可以通过建立一棵左右的二叉树然后从左向右遍历直到找到一个孔来加快处理速度,但这实际上与排序的复杂度相同,因为它是排序的。而且您可能不会想出比Timsort更快的方法


1
您是说遍历列表与排序的时间复杂度相同吗?
maple_shaft

@maple_shaft:不,我是说根据随机数据构建一棵二叉树,然后从左向右遍历,等效于排序然后从小到大遍历。
pillmuncher

1

这里的大多数想法不只是排序。位向量版本是普通的Bucketsort。还提到了堆排序。基本上可以归结为选择正确的排序算法,该算法取决于时间/空间要求以及元素的范围和数量。

在我看来,使用堆结构可能是最通用的解决方案(堆基本上可以为您提供最小的元素,而无需进行完整的排序)。

您还可以分析以下方法:首先找到最小的数字,然后扫描大于该数字的每个整数。或者,您找到5个最小的数字,希望它们之间可以有一个间隔。

所有这些算法的强度取决于输入特性和程序要求。


0

不使用额外存储空间或假设整数宽度(32位)的解决方案。

  1. 在一次线性扫描中,找到最小的数字。让我们称之为“分钟”。O(n)时间复杂度。

  2. 选择一个随机的枢轴元素并进行快速排序样式分区。

  3. 如果枢轴最终到达位置=(“ pivot”-“ min”),则在分区的右侧递归,否则在分区的左侧递归。这里的想法是,如果从头开始没有孔,则枢轴将位于第(“ pivot”-“ min”)位置,因此第一个孔应位于分隔壁的右侧,反之亦然。

  4. 基本案例是由1个元素组成的数组,并且孔位于该元素和下一个元素之间。

预期的总运行时间复杂度为O(n)(带常数的8 * n),最坏的情况为O(n ^ 2)。在这里可以找到类似问题的时间复杂度分析。


0

我相信,如果可以保证没有重复*,那么我想出了一些应该可以普遍有效地工作的东西(但是,它应该可以扩展到任意数量的孔和整数范围)。

该方法背后的想法就像快速排序,因为我们找到了一个枢轴并对其进行了分区,然后在有孔的一侧递归。要查看哪一侧有孔,我们找到最低和最高的数字,然后将其与该侧的支点和数值个数进行比较。假设枢轴为17,最小数量为11。如果没有孔,则应该有6个数字(11、12、13、14、15、16、17)。如果有5,我们知道在那一侧有一个洞,我们可以在那一侧递归找到它。我很难对此进行更清楚的解释,所以让我们举个例子。

15 21 10 13 18 16 22 23 24 20 17 11 25 12 14

枢:

10 13 11 12 14 |15| 21 18 16 22 23 24 20 17 25

15是枢轴,用管道(||)表示。枢轴的左侧有5个数字,应为(15-10),右侧为9,应有10(25-15)。因此,我们在右侧进行递归;我们会注意到,如果孔与之相邻,则前一个边界为15(16)。

[15] 18 16 17 20 |21| 22 23 24 25

现在左侧有4个数字,但应该有5个(21-16)。因此,我们在那里递归,并再次注意上一个边界(在方括号中)。

[15] 16 17 |18| 20 [21]

左侧有正确的2个数字(18-16),但右侧有1个而不是2(20-18)。根据我们的结束条件,我们可以将1的数字与两边的数字(18,20)进行比较,看看是否缺少19或再次递归一次:

[18] |20| [21]

左侧的大小为零,在枢轴(20)和先前的边界(18)之间有间隙,因此19是孔。

*:如果存在重复项,则可以使用哈希集在O(N)的时间内将其删除,同时将整个方法保留为O(N),但这可能比使用其他方法花费更多的时间。


1
我不相信OP会说只有一个洞。输入是未排序的数字列表-它们可以是任何数字。从您的描述中尚不清楚如何确定“应该”有多少个数字。
Caleb 2012年

@caleb有多少个孔都没有关系,只是没有重复(可以在O(N)中使用哈希集将其删除,尽管实际上它可能比其他方法有更多的开销)。我尝试改善描述,看是否更好。
凯文(Kevin)

这不是线性的,IMO。更像是(logN)^ 2。在每一步中,您都要旋转所关心集合的子集(已识别为具有第一个“孔”的先前子数组的一半),然后递归到左侧的任何“孔”中,或右侧(如果没有)。(logN)^ 2仍然比线性更好;如果N增加十倍,则您仅需执行2(log(N)-1)+ 1个步骤。
KeithS 2012年

@Keith-不幸的是,您必须查看每个级别上的所有数字以进行旋转,因此大约需要n + n / 2 + n / 4 + ... = 2n个(技术上为2(nm))比较。
凯文
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.