【问题标题】:Smallest missing integer algorithm that runs in O(n)?在 O(n) 中运行的最小缺失整数算法?
【发布时间】:2017-02-22 22:22:29
【问题描述】:

什么算法可以在 O(n) 时间内从数组中找到缺失的整数?

假设我们有一个数组 A,其元素的值范围为 {1,2,3...2n}。缺少一半的元素,因此长度为 A = n。

例如:

A = [1,2,5,3,10] , n=5

输出 = 4

【问题讨论】:

  • 这是作业吗?
  • 不,我学物理。这是业余时间。我从网上的家庭作业中找到了这个问题......
  • @hanko 允许多少额外存储空间:例如拥有一组标志很简单 - 如下面的答案所示。但是如何处理这个仅 O(1) 的额外存储并不明显。
  • 我有一个真正使用 O(1) 额外存储和 O(N) 时间的解决方案,而不会从阵列中窃取位。这是快速选择的一种变体。

标签: algorithm sorting search


【解决方案1】:

最小的缺失整数必须在 [1, ..., n+1] 范围内。所以创建一个标志数组,最初都是假的,表示该整数的存在。那么一个算法就是:

  1. 扫描输入数组,在遇到范围内的值时将标志设置为真。这个操作是 O(n)。 (也就是说,对于输入数组中的每个位置i,将flag[A[i]] 设置为true,提供A[i] <= n。)
  2. 扫描标志数组以查找第一个错误标志。这个操作也是 O(n)。第一个错误标志的索引是最小的缺失整数。

编辑:O(n) 时间算法与 O(1) 额外空间:

如果A 是可写的,并且A 的元素中有一些额外的位可用,那么常量额外空间算法是可能的。例如,如果A 的元素是有符号值,并且由于所有数字都是正数,我们可以使用原始数组中数字的符号位作为标志,而不是创建一个新的标志数组。所以算法是:

  1. 对于原始数组的每个位置i,如果abs(A[i]) < n+1,则将A[abs(A[i])]处的值设为负数。 (这假设数组索引基于 1。如果您使用基于 0 的数组,则以明显的方式进行调整。)不要只是否定该值,以防A 中存在重复值。
  2. 查找A 的第一个正数元素的索引。该索引是A 中最小的缺失数字。如果所有位置都是负数,则 A 必须是 {1, ..., n} 的排列,因此最小的缺失数是 n+1

如果元素是无符号的,但可以保存高达 4 n + 1 的值,那么在步骤 1 中,不要将元素设为负数,而是添加 2 n + 1(如果元素是 n)并使用(A[i] mod (2n+1)) 而不是abs(A[i])。然后在第 2 步中,找到第一个元素 n + 1 而不是第一个正元素。其他类似的技巧也是可能的。

【讨论】:

  • 这是一个显而易见的答案,但需要 O(n) 额外 存储空间。使用 o(1) 额外存储空间的任何方法(我不确定)。
  • 这种方法不需要先对数组进行排序吗?
  • @hanko:不,你不会。是什么让你认为你会?
  • @javadba - 不是我能想到的。
  • @AnT - 好吧,您可以使用其他技巧,例如将 2n+1 添加到原始数组中的值以代替设置标志(并使用 A[i] mod 2n+1绝对值(A[i]))。由于 n 是可变的,因此任何特定 n 的某处都可能有额外的位。如果不是,那么 O(1) 空间算法当然无法实现。
【解决方案2】:

您可以在 O(1) 额外空间中执行此操作,假设对数组的唯一有效操作是读取元素和交换元素对。

首先请注意,问题的规范排除了数组包含重复项的可能性:它包含从 1 到 2N 的数字的一半。

我们执行快速选择类型算法。从 m=1,M=2N+1 开始,将数组旋转到 (m + M)/2。如果数组左侧部分的大小(元素

每次考虑的数组切片的大小减半,旋转大小为 n 的数组可以在 O(n) 时间和 O(1) 空间内完成。所以总的来说,时间复杂度是 2N + N + N/2 + ... + 1

【讨论】:

  • 不错的答案:利用问题的特征来找到看似最优的解决方案。
  • 不错的方法,但我认为算法需要一些调整。假设 n = 4 且 A 为 [1, 2, 3, 4]。然后在第一次迭代中,m = 1,M = 9,第一个枢轴将在 5 上。这将产生分区 [1, 2, 3, 4], []。由于左边部分有 4 个元素,小于 (m + M)/2 - m + 1 = 5,所以第一个缺失的元素应该在 [1, 2, 3, 4] 中。但事实并非如此。
  • @TedHopp 左半部分应该包含 5 个元素(元素
  • 是的,我想在第一次迭代中,必须选择左边的部分。第一个枢轴值是 n+1 并且数组中只有 n 个元素。不过,正因为如此,我认为复杂性分析公式不太正确(至少对于系数而言;它仍然是 O(n))。
  • 该死的,这太棒了。谢谢。
【解决方案3】:

Paul Hankin 在 C++ 中的想法的 implementation

#include <iostream>
using namespace std;
const int MAX = 1000;
int a[MAX];
int n;

void swap(int &a, int &b) {
    int tmp = a;
    a = b;
    b = tmp;
}

// Rearranges elements of a[l..r] in such a way that first come elements 
// lower or equal to M, next come elements greater than M. Elements in each group
// come in no particular order.
// Returns an index of the first element among a[l..r] which is greater than M.
int rearrange(int l, int r, int M) {
    int i = l, j = r;
    while (i <= j)
        if (a[i] <= M) i++;
        else swap(a[i], a[j--]);
    return i;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];

    int L = 1, R = 2 * n;
    int l = 0, r = n - 1;
    while (L < R) {
        int M = (L + R) / 2; // pivot element
        int m = rearrange(l, r, M); 
        if (m - l == M - L + 1) 
            l = m, L = M + 1;
        else
            r = m - 1, R = M;
    }

    cout << L;
    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-24
    • 2015-07-04
    • 2015-08-11
    • 1970-01-01
    • 1970-01-01
    • 2020-01-23
    • 2019-04-27
    相关资源
    最近更新 更多