在巨大的正整数列表中查找出现奇数次的所有值

时间:2015-10-07 12:42:23

标签: list hash

我从一位同事那里得到了这个问题。

问:给出一个巨大的列表(比如数千个)正整数&有多个值在列表中重复,如何找到那些奇数次的值? 喜欢1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 1 2 3 4 5 6 1 2 3 4 5 1 2 3 4 1 2 3 1 2 1 ... 这里, 1次发生8次 2次出现7次(必须在输出中列出) 3次发生6次 4次出现(必须在输出中列出)

&安培;所以...(上面的一组值仅用于解释问题,但实际上列表中的任何顺序都有任何正数)。

最初我们正在寻找一种逻辑(基于c)。

我建议如下,

  1. 使用哈希表和列表中的值作为表的索引/键,每次在遍历列表时遇到值时,不断更新相应索引中的计数;但是,如何决定哈希表的大小?虽然它可能要求Hashtable与列表一样大,但我无法说出来。

  2. 一旦列表走过&填充哈希表(使用'计数'每个值/索引的出现次数),只有找到/列出奇数次出现值的方法是遍历表&找到它?这是唯一的办法吗?

  3. 在这种情况下,这可能不是最佳解决方案。 你能否建议任何其他有效的方法呢?

    我在SO中搜索过,但是有一些查询/回复是关于找到单个值出现奇数次但没有一个像我提到的那样。

    这个问题的相关性尚不清楚,但似乎在他的采访中被问到...... 请建议。

    谢谢,

2 个答案:

答案 0 :(得分:0)

如果要计算的值受到中等合理限制的限制,那么您可以创建一个计数器数组,并使用这些值作为数组索引计算。你不需要紧张的约束,并且“合理的”#34;有点是平台问题。我会毫不犹豫地将这种方法用于所有uint16_t值的边界(因此数组大小),并且这不是一个硬限制:

#define UPPER_BOUND 65536

uint64_t count[UPPER_BOUND];

void count_values(size_t num_values, uint16_t values[num_values]) {
    size_t i;

    memset(count, 0, sizeof(count));
    for (i = 0; i < num_values; i += 1) {
        count[values[i]] += 1;
    )
}

但是,由于您只需跟踪偶数与奇数,因此输入中每个不同的值实际上只需要一位。把它压得那么远有点极端,但这并不是那么糟糕:

#define UPPER_BOUND 65536

uint8_t odd[UPPER_BOUND];

void count_values(size_t num_values, uint16_t values[num_values]) {
    size_t i;

    memset(odd, 0, sizeof(odd));
    for (i = 0; i < num_values; i += 1) {
        odd[values[i]] ^= 1;
    )
}

最后,如果值odd[i]出现奇数次,则1包含i,如果0出现偶数,则i包含{{1}}次数。

另一方面,如果要计数的值分布如此广泛以至于数组需要太多内存,那么哈希表方法似乎是合理的。但是,在这种情况下,你提出了错误的问题。而不是

  

如何决定哈希表的大小?

你应该问一些&#34;什么哈希表实现并不要求我手动管理表大小?&#34;有几个。就个人而言,我已成功使用UTHash,但最近它已不再维护。

您还可以使用按顺序维护的链接列表或搜索树。毫无疑问,还有其他可行的选择。

你也问了

  

一旦列表走过了&amp;填充哈希表(使用&#39;计数&#39;每个值/索引的出现次数),只有找到/列出奇数次出现值的方法是遍历表&amp;找到它?那是唯一的办法吗?

如果你通过我们到目前为止讨论过的一般方法进行分析,那么,读出结果的唯一方法是迭代计数。我可以想象一种替代的,更复杂的方法,你可以在那些具有偶数计数和奇数计数的列表之间切换数字,但是我很难看到你在读数中获得的效率如何可能无法被淹没计数阶段的效率损失。

答案 1 :(得分:0)

在特定情况下,您可以遍历列表并在集合中切换值的存在。结果集将包含出现奇数次的所有值。但是,这仅适用于该特定谓词,如果您想要所有出现偶数次的条目,则需要您描述的更通用的count-then-filter算法。

两种算法都应该是O(N)时间和最坏情况下的O(N)空间,并且基于集合的算法的常数可能更低,但是您需要进行基准测试它反对你的数据。在实践中,除非存在明显的性能问题,否则我将使用更通用的算法。