生成与数组的1000个元素不同的新元素

时间:2011-09-14 08:13:15

标签: c++ arrays algorithm

我在接受采访时被问到这个问题。考虑穿孔卡的情况,其中每个穿孔卡具有64位模式。我被建议每张卡片为int,因为每个int都是一个位集合。

另外,我认为我有一个已经包含1000张这样的牌的阵列。我必须每次生成一个新元素,这与之前的1000张卡片不同。数组中的整数(也就是卡片)不一定要排序。

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

13 个答案:

答案 0 :(得分:5)

有2个 64 64位整数,这个数字非常多 大于1000,最简单的解决方案就是生成一个 随机64位数,然后验证它不在表中 已生成数字。 (它的概率是 无穷小,但你可以肯定。)

由于大多数随机数生成器不生成64位值,因此 留下你自己写的,或(更简单),结合 值,例如通过生成8个随机字节,并memcpy将它们放入a uint64_t

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

答案 1 :(得分:4)

我可能会遗漏一些东西,但大多数其他答案在我看来都过于复杂。 只需对原始数组进行排序,然后从零开始计数:如果当前计数在数组中跳过它,否则你有下一个数字。该算法是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;
    }
  }
}

答案 2 :(得分:2)

这是 O(n)算法:

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

注意:正如@a​​mit在下面指出的那样,如果INT64_MAX已经在列表中,则会失败。

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

答案 3 :(得分:2)

@arne几乎就在那里。您需要的是self-balancing interval tree,可以在O( n lg n )时间内构建。

然后选择顶部节点,它将存储一些间隔[ i j ]。根据区间树的属性, i -1和 j +1都是新密钥的有效候选者,除非 i = {{ 1}}或 j = UINT64_MIN。如果两者都为真,那么您已经存储了2 ^ 64个元素,并且您无法生成新元素。存储新元素,该元素需要O(lg n )最坏情况时间。

I.e。:init需要O( n lg n ),生成需要O(lg n )。两者都是最糟糕的数字。这种方法的最大优点是顶级节点将保持“增长”(存储更大的间隔)并与其后继者或前任节点合并,因此树实际上将在内存使用方面缩小并最终每次操作的时间衰减到O(1)。你也不会浪费任何数字,所以你可以继续生成,直到你有2 ^ 64个。

答案 4 :(得分:2)

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

  1. 对数字进行排序
  2. 创建包含间隔[u, v]
  3. 的链接列表
  4. 插入[1, first number - 1]
  5. 对于其余每个号码,请插入[prev number + 1, current number - 1]
  6. 插入[last number + 1, int64_max]
  7. 您现在有一个列表,表示未使用的数字。您可以简单地迭代它们以生成新数字。

答案 5 :(得分:0)

我认为要走的路是使用某种散列。因此,您可以根据MOD操作说明将卡存储在某些存储桶中。在您创建某种索引之前,您将无法循环遍历整个数组。

如果你看一下java中的HashSet实现,你可能会得到一个线索。

编辑:我假设您希望它们是随机数,如果您不介意下面的序列MAX + 1是很好的解决方案:)

答案 6 :(得分:0)

您可以构建已存在元素的二叉树并遍历它,直到找到深度不是64且节点少于两个的节点。然后,您可以构造一个“缺少”的子节点并拥有一个新元素。如果我没有弄错的话,应该相当快,按大约O(n)的顺序。

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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

插入: 使用递增索引到新数组。如果此索引处新数组中的值不是列表中第一个元素的值,请将其添加到列表中,否则请检查新数组中下一个位置的值。 当新阵列用完时,使用1000..1999重新填充它并使上述现有值无效。是的,这是循环遍历列表,但不必为每次插入完成。

在O(1)附近,直到列表变得如此之大,偶尔迭代它以使'new'新数组无效变得很重要。也许你可以通过使用一个增长的新数组来缓解这种情况,也许总是与列表一样大小?

RGDS, 马丁

答案 9 :(得分:0)

将它们全部放入大小为&gt;的哈希表中1000,找到空单元格(这是停车问题)。为此生成密钥。这当然会更好地适应更大的桌面尺寸。该表只需要1位条目。

编辑:这是鸽子原则。 对于散列函数,这需要“modulo tablesize”(或其他一些“半可逆”函数)。

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

答案 10 :(得分:0)

根据此处的解决方案:question on array and number

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

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

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

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

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

新数字为O(1),最初为O(n)。

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

答案 11 :(得分:0)

如此简单的事情:

1)将数组划分为等于1000以下的数字

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

3)否则我们知道下层分区中不存在1到1000之间的数字。

4)创建1000个元素的bool数组,或1000个元素的长位域,或者诸如此类的数据并将数组初始化为全0的

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

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

这可以在O(n)时间内工作,或者因为我们已经将所有内容限制在1000,技术上它是O(1),但一般来说是O(n)时间和空间。数据有三次传递,这不一定是最优雅的方法,但复杂性仍为O(n)。

答案 12 :(得分:-1)

你可以使用不在原始数组中的数字创建一个新数组,然后从这个新数组中选择一个。

¿O(1)?