生成一个不同于数组中1000个元素的新元素

Generate a new element different from 1000 elements of an array

本文关键字:元素 1000个 新元素 不同于 一个 数组      更新时间:2023-10-16

我在一次采访中被问到这个问题。考虑穿孔卡片的场景,其中每个穿孔卡片都有64位模式。有人建议我把每张卡都作为int,因为每个int都是一个位的集合。

此外,考虑到我有一个数组,其中已经包含1000张这样的卡片。我每次都要生成一个新的元素,这与之前的1000张卡不同。数组中的整数(又名卡片(不一定要排序。

更重要的是,对于C++来说,这怎么可能呢?64 bit int来自哪里?我如何从要生成的元素与数组中已经存在的所有元素不同的数组中生成这个新卡?

有2个6464位整数,这个数字太多了大于1000,最简单的解决方案是只生成随机64位数字,然后验证它是否不在的表中已经生成的数字。(可能性是无穷小,但你也可以确定。(

由于大多数随机数生成器不会生成64位值,因此剩下的要么自己写,要么(简单得多(结合值,比如生成8个随机字节,然后memcpy将它们转换为uint64_t

至于验证号码是否已经存在,std::find为只适用于一两个新数字;如果你必须做很多查找、排序表和使用二进制搜索值得的或者某种哈希表。

我可能遗漏了一些东西,但大多数其他答案在我看来过于复杂。只需对原始数组进行排序,然后从零开始计数:如果当前计数在数组中,请跳过它,否则您就有了下一个数字。这个算法是O(n(,其中n是新生成的数字的数量:对数组进行排序和跳过现有数字都是常数。这里有一个例子:

#include <algorithm>
#include <iostream>
unsigned array[] = { 98, 1, 24, 66, 20, 70, 6, 33, 5, 41 };
unsigned count = 0;
unsigned index = 0;
int main() {
  std::sort(array, array + 10);
  while ( count < 100 ) {
    if ( count > array[index] )
      ++index;
    else {
      if ( count < array[index] )
        std::cout << count << std::endl;
      ++count;
    }
  }
}

这里有一个O(n(算法:

int64 generateNewValue(list_of_cards)
{
    return find_max(list_of_cards)+1;
}

注意:正如@amit在下面指出的,如果INT64_MAX已经在列表中,则此操作将失败

据我所知,这是获得O(n(的唯一方法。如果你想处理(相当重要的(边缘情况,那么你必须进行某种适当的排序或搜索,这将带你进入O(n log n(

@arne就快到了。您需要的是一个自平衡区间树,它可以在O(nlgn(时间内构建。

然后取顶部节点,它将存储一些区间[ij]。根据区间树的性质,i-1和j+1都是新密钥的有效候选者,除非i=UINT64_MINj=UINT64_MAX。如果两者都为真,那么您已经存储了2^64个元素,并且不可能生成新的元素。存储新元素,这需要O(lgn(最坏的时间。

即:init取O(nlgn(,generate取O(lgn(。两者都是最坏的情况。这种方法最棒的地方是,顶部节点将保持"增长"(存储更大的间隔(,并与其继任者或前任合并,因此树在内存使用方面实际上会收缩,最终每次操作的时间衰减为O(1(。你也不会浪费任何数字,所以你可以一直生成,直到你有2^64个数字。

该算法具有O(N lg N)初始化、O(1)查询和O(N)内存使用。我假设你有一些整数类型,我将其称为int64,它可以表示整数[0, int64_max]

  1. 对数字排序
  2. 创建包含间隔[u, v]的链接列表
  3. 插入[1, first number - 1]
  4. 对于剩余的每个数字,插入[prev number + 1, current number - 1]
  5. 插入[last number + 1, int64_max]

现在,您有一个表示未使用的数字的列表。您可以简单地对它们进行迭代以生成新的数字。

我认为应该使用某种哈希。所以你可以根据MOD操作将你的卡存储在一些桶中。在创建某种索引之前,你会被整个数组的循环所困扰。

如果您了解一下java中的HashSet实现,您可能会得到一些线索。

编辑:我想你希望它们是随机数,如果你不介意下面的序列MAX+1是一个好的解决方案:(

您可以构建一个现有元素的二进制树,并遍历它,直到找到一个深度不为64且子节点少于两个的节点。然后,您可以构造一个"丢失"的子节点并拥有一个新元素。如果我没有弄错的话,应该相当快,大约在O(n(的顺序。

bool seen[1001] = { false };
for each element of the original array
    if the element is in the range 0..1000
        seen[element] = true
find the index for the first false value in seen

初始化:不要对列表进行排序。创建一个1000长的包含0..999的新数组。对列表进行迭代,如果任何数字在0..999范围内,则将新数组中的值替换为列表中第一项的值,使其在新数组中无效。

插入:对新数组使用递增索引。如果此索引处的新数组中的值不是列表中第一个元素的值,请将其添加到列表中,否则请检查新数组中下一个位置的值。当新数组用完时,使用1000.1999重新填充,并如上所述使现有值无效。是的,这是在列表上循环,但不必每次插入都这样做。

接近O(1(,直到列表变得如此之大,以至于偶尔迭代它以使"新"新数组无效变得非常重要。也许你可以通过使用一个不断增长的新数组来缓解这种情况,也许蜜蜂总是列表的大小?

Rgds,Martin

将它们全部放入一个大于1000的哈希表中,并找到空单元格(这是停车问题(。为此生成密钥。当然,对于更大的桌子来说,这会更好。该表只需要1位条目。

编辑:这就是鸽子洞原理。这需要哈希函数的"模表大小"(或其他"半可逆"函数(。

unsigned hashtab[1001] = {0,};
unsigned long long long long numbers[1000] = { ... };
void init (void)
{
unsigned idx;
for (idx=0; idx < 1000; idx++) {
    hashtab [ numbers[idx] % 1001 ] += 1; }
}
unsigned long long long long generate(void)
{
unsigned idx;
for (idx = 0; idx < 1001; idx++) {
    if ( !hashtab [ idx] ) break;  }
return idx + rand() * 1001;
}

基于这里的解决方案:关于数组和数字的问题

由于有1000个数字,如果我们考虑它们的余数为1001,那么至少会缺少一个余数。我们可以选择它作为我们丢失的号码。

因此,我们保持一个计数数组:C[1001],它将保持C[r]中余数r(除以1001(的整数的数量。

我们还维护了一组C[j]为0的数字(比如使用链表(。

当我们移动窗口时,我们递减第一个元素的计数(比如余数i(,即递减C[i]。如果C[i]变为零,我们将i加到这组数字上。我们用添加的新数字更新C数组。

如果我们需要一个数字,我们只需从C[j]为0的j集合中选择一个随机元素。

对于新数字,这是O(1(,最初是O(n(。

这与其他解决方案类似,但并不完全相同。

像这样简单的东西怎么样:

1( 将数组划分为1000以下和以上的数字

2( 如果所有的数字都在下分区内,那么选择1001(或任何大于1000的数字(,我们就完成了。

3( 否则,我们知道一定存在一个在1到1000之间的数字,而这个数字在下分区中不存在。

4( 创建一个由bools组成的1000元素数组,或一个1000元素长的位字段,或诸如此类的东西,并将数组初始化为所有0的

5( 对于下分区中的每个整数,使用其值作为数组/位字段的索引,并将相应的布尔设置为true(即:进行基数排序(

6( 遍历数组/位字段并选择任何未设置值的索引作为解决方案

这在O(n(时间内有效,或者因为我们将所有事物都限制在1000以内,所以从技术上讲,它是O(1(,但一般来说是O(n(时间和空间。数据有三次传递,这不一定是最优雅的方法,但复杂性仍然是O(n(。

您可以用原始数组中没有的数字创建一个新数组,然后从这个新数组中选择一个。

?O(1(?