【问题标题】:Most common element in an array / Finding the relative majority, deterministically in O(n) time and O(1) space?数组中最常见的元素/确定性地在 O(n) 时间和 O(1) 空间中找到相对多数?
【发布时间】:2016-04-09 23:51:33
【问题描述】:

例如,数组的答案:

1、11、3、95、23、8、1

将是 1,因为所有其他元素只出现一次,而 1 出现两次。

我在 stackoverflow 上看到的许多与此问题类似的问题都要求找到绝对多数(答案在长度为 n 的数组中至少出现 n/2),或者使用排序或哈希表。前者不是我要的,后者要么太慢( O(n log n) 用于排序)或使用太多内存( O(n) 用于哈希表)。

这样的算法存在吗?如果不是,是否有证据表明为什么这是不可能的?包括一个来源会很好。

【问题讨论】:

  • 线性扫描怎么样?看来您对此没有任何想法。
  • 你能解释一下你所说的线性扫描是什么意思吗,不,这不是功课
  • O(1) 空间似乎是一个很大的挑战
  • 如果您的数组包含整数,那么您可以在 O(n) 时间和 O(1) 空间内完成。使用就地二进制基数排序对数组进行排序。然后遍历数组以确定最常见的元素。基数排序需要 32 次遍历数组,所以总共 33 次遍历,这是一个常数,所以 O(n)。

标签: algorithm data-structures


【解决方案1】:

如果你想有固定的空间来找到最常见的元素,你需要有一个元素的最大位数。如果您不这样做,那么大型输入数组可能具有更大的输入数字,因此表示数字的位大于您存储结果的固定空间。

假设k 是您支持的最大数的长度。如果您尝试天真地创建一个 2^k 存储桶数组来计算每个数字的出现次数(计数器排序),您可能会收到一个包含相同数字的数组,在这种情况下,您的算法最终需要 log(n) 空间来存储总和。[*]

如果我们看一个更简单的问题版本 - 确定输入中是否有更多 10,我认为您需要一个堆栈来执行此操作(您存储了多少10 领先),因此即使我们将输入长度限制为 k = 1 位大小也是不可能的。

您的问题更笼统(k > 1,但仍然是固定的),并且还需要非常量的空间,所以这是不可能的,因为问题的措辞。

[*] 如果您假设计数器具有O(1) 空间复杂度,那么您可以采用计数器排序方法,尽管这样做您已经为输入数组的最大大小设置了一个上限(可能或可能不可接受):就k而言,您的数组的输入元素的最大位数以及c您的计数器中的最大位数,您的数组最多可以有2^k * 2^c元素(其中一个计数器将在下一个元素上溢出)。为了解决这个问题,您可以添加一个O(1) 时间步来减少您的计数器,以便在处理每个元素后,如果所有计数器都不是0,则最小值始终为0,从而使它们相对而不是绝对。这需要O(1) 时间,因为如果所有元素都不为零,则只需在每个元素上执行它时将O(2^k) = O(1) 计数器减少1。虽然该算法现在可以处理一些任意大的输入,但任何具有子数组的输入数组使得两个值 ab 使得使用计数器策略的 count(a) - count(b) > 2^c = max(counter) 对于某些输入将失败。事实上,依赖O(1) 空间复杂度计数器方法的结果是,所有以2^c + 1 相同元素开头的数组都无法由该算法处理。

【讨论】:

  • 问题说哈希表是 O(n) 内存,所以我们必须考虑一个计数器是 O(1) 内存。因此,至少 0/1 的情况应该很容易。
  • @ThomasAhle 我认为 OP 的意思是,如果我有一个包含 n 项目的输入数组,那么一个存储我是否见过数字的哈希表最多占用 n 项目它在其最终状态(由于超过其阈值而调整大小之后)是O(n) 内存。 O(1)“计数器”相当于说我是否看过它 - 不是说我看过多少的计数器……除非 OP 另有指示。
  • 好的,一个位向量只需要 O(n) 位。哈希表通常是 nlogn,即使值是位,因为它需要存储用于相等检查的键。 (也许完美的散列有帮助)。但是在帮助解决问题之前知道我们是否已经看到了一个值呢?看来我们需要知道计数。
  • @ThomasAhle 当您说“哈希表通常是 nlogn ...”时,您的“n”是什么?它不能帮助我们解决问题;我们需要计数(或者实际上是提前多少,以防它后来落后 - 你不需要计算实际计数来计算多数) - 你是对的 - 但我认为 OP 可能没有'不知道什么数据结构最终对这个问题有用或无用;我认为 OP 的目的是用一个“违反规则”的例子来澄清。
  • 是的,OP 意味着表存储计数,并考虑了这个 O(n) 内存。因此,一个计数器是 O(1) 内存。这就是我的观点。
【解决方案2】:

使用这里的想法:

How can we find a repeated number in array in O(n) time and O(1) space complexity

并应用类似于counting sort 的技术。也就是说,创建 N 个 bin(一个大小为 N 的数组),其中 N 是您期望遇到的最大整数。这仍然是 O(1) 空间。然后,在 O(n) 时间内遍历原始数组,当遇到值 i 时,将索引 i 处的结果数组增加 1。然后,遍历结果数组(再次 O(1) 次),找到最大的单个值。该值的索引将是原始列表中最常见的值。

【讨论】:

  • 可行吗?如果输入是一个包含两个整数 { int.MinValue, int.MaxValue } 的数组怎么办? n = 2,但您需要创建一个非常大的数组。
  • 这是假设输入的大小远大于输入的最大值(在实践中通常是正确的)。否则,您可以事先再次通过数组,并确保不是这种情况——如果是,请使用另一种算法。这不会改变运行时。
  • 您的假设似乎是有限且不自然的。您可以对值的范围做出的唯一假设是,无论数组的大小如何,它们都受到 int 的限制。
  • 我认为我不同意的部分是您的方法是 O(1) 空间。鉴于输入必须是整数的限制,您创建的数组的大小因输入而异。所以它不是恒定的。可以说它是恒定的(因此 O(1))的唯一方法是,如果你说你总是创建一个大小等于离散 int 值的数量(4+十亿)的数组,这不是那么有用。我不认为“为这个输入尝试其他东西”的解决方法有帮助,除非你明确指定其他东西是什么。
【解决方案3】:

这不是一个完整的答案,但它应该有助于阐明为什么这个问题很困难。

假设我们想要设计一种算法,它会扫描数组(以某种顺序)以找到最常见的元素。在我们的算法运行期间,允许保留一些数据结构S。让我们看看S 中有多少信息,因此我们是否可以将它包含在O(1) 内存中。

假设我们的算法已经处理了数组的第一个 k 元素。现在S 可以告诉我们a[0..k] 范围内最常见的元素。然而,假设我们知道k+1'st 元素,那么我们也会知道a[0..k+1] 范围内最常见的元素。如果不能,如果nk+1,我们的算法将无法工作。更一般地,考虑到元素a[k..m]S 的知识,我们知道a[0..m] 中最常见的元素。

我们可以使用上述参数从S 中提取信息。假设我们正在处理[0,u] 范围内的整数(如果原始数组占用空间O(n),则必须有一些范围)。如果最初最常见的元素是5,那么我们添加0,直到最常见的元素发生变化。如果这将c 设为零,则a[0..k] 必须包含c5 更多的0。重复这个论点,我们得到了很多线性方程,我们可以求解这些方程来准确判断每个元素 [0,u]a[0..k] 中出现的次数。

这告诉我们,任何进行扫描的数据结构都可以存储所有可见元素的计数(以某种压缩方式)。如果您对数学感兴趣,看到n 数字后存储的是log(n+u-1 choose n),这是将n 不可区分项划分为u 可区分箱的方式数的日志。这不仅仅是log(u^n/n!) >= nlogu-nlogn

结论:任何只执行一次数组传递的算法都必须使用尽可能多的内存来存储目前看到的所有计数。如果nu 小,这对应于存储n 内存字。

(好吧,我们也可以覆盖现有数组,而不是额外的内存)。

这里还有很多值得探索的地方。例如。多次传递如何影响上述论点。但是我认为我应该在这一点上停下来:),但在我看来,任何线性时间算法(具有较大的u)都不太可能摆脱O(1) 的额外内存。

【讨论】:

  • 我应该提到这与 maxko87 的解决方案有何关系。 Maxko87 扫描数组并将计数保存在大小为ulogn 位的数组中(u bins 每个都可以计数到n)。如果n 的大小与u 差不多,那么他的解决方案就很好。如果n 小于u,他的解决方案仍然可以使用hashmap 来提高效率,但它不会是恒定空间。
  • 这是同一件事的另一个证明theory.stanford.edu/~trevisan/cs154-12/notestream.pdf
  • 你可以做不止一次,仍然是 O(n)... (对任何人的评论 - 在这个答案中提供有价值的信息,但要知道假设只有 1 次通过比要求更严格O(n),这就是问题所要求的)
  • 对,这完全取决于型号。然后输入是否存储在只读存储器中也很重要。否则,基数排序方法将起作用。我不知道这会对应什么实际情况。
  • 换一种说法——破坏你的输入数组意味着你的算法的多次运行将需要一个在时间和空间上初始化的数组,所以就像你说的那样,这不切实际。具有预先初始化的只读内存可以存在于算法的连续或并行运行中,因此您预先进行的初始化是一次性的。如果我有一些不可变的数据结构,我可以编写小型紧凑的空间和时间算法来并行分析它,一遍又一遍......
【解决方案4】:

这是我读取数组中最常见元素的脚本

<?php

class TestClass {

    public $keyVal;
    public $keyPlace = 0;

    //put your code here
    public function maxused_num($array) {
        $temp = array();
        $tempval = array();
        $r = 0;
        for ($i = 0; $i <= count($array) - 1; $i++) {
            $r = 0;
            for ($j = 0; $j <= count($array) - 1; $j++) {
                if ($array[$i] == $array[$j]) {
                    $r = $r + 1;
                }
            }
            $tempval[$i] = $r;
            $temp[$i] = $array[$i];
        }
        //fetch max value
        $max = 0;
        for ($i = 0; $i <= count($tempval) - 1; $i++) {
            if ($tempval[$i] > $max) {
                $max = $tempval[$i];
            }
        }
        //get value 
        for ($i = 0; $i <= count($tempval) - 1; $i++) {
            if ($tempval[$i] == $max) {
                $this->keyVal = $tempval[$i];
                $this->keyPlace = $i;
                break;
            }
        }

        // 1.place holder on array $this->keyPlace;
        // 2.number of reapeats $this->keyVal;
        return $array[$this->keyPlace];
    }

}

$catch = new TestClass();
$array = array(1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 1, 2, 3, 1, 1, 2, 5, 7, 1, 9, 0, 11, 22, 1, 1, 22, 22, 35, 66, 1, 1, 1);
echo $catch->maxused_num($array);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    • 1970-01-01
    • 2020-01-28
    • 2011-08-09
    • 1970-01-01
    • 2012-01-02
    • 2018-03-08
    相关资源
    最近更新 更多