【问题标题】:Duplicate removal重复删除
【发布时间】:2017-09-21 02:54:18
【问题描述】:

说实话,这是一个硬件问题。

整个问题:

使用 C++/Java 在 O(n) 时间复杂度且没有额外空间的一维数组中实现重复删除算法。例如,如果输入数组是 {3,5,5,3,7,8,5,8,9,9},那么输出应该是 {3,5,7,8,9}。

想了好久还是没解决。

我的想法:

  1. 如果数组已排序,我可以删除 O(n) 中的重复项。但我所知道的最快的排序算法复杂度为 O(n*log(n))。

  2. 一种以 O(n) 进行排序的算法是 bin 或 bucket 排序。这里的问题是如果不使用额外的空间就无法实现。

  3. 我想知道这是否可能。

我研究了一下,没有发现任何新东西。

感谢您的帮助。

P.S.:如果不是明天考试,我会给它更多时间。

【问题讨论】:

  • 哈希表可以在 O(1) 时间内告诉你一个元素是否存在于它的集合中。那应该为您指明正确的方向
  • 但是,哈希表需要空间,它说没有多余的空间
  • @MartinLiversage 好点,我错过了这部分。
  • 我曾想过 O(n) 解决方案,例如使用 bool 数组,但这会占用空间。我真的需要完成它。
  • 空间 O(1) 和时间 O(n) 的要求如果不是不可能的话,真的很难。但是,我发现an algorithm 适用于整数并声称在这些限制范围内(至少在正常情况下)。它使用递归,但是可以使用尾调用来完成,不需要额外的内存。

标签: arrays algorithm duplicates


【解决方案1】:

确实可以,用in-place radix sort就行了。

它针对O(kn) 运行,其中k 对于任何标准数值数据类型都是常量,并且需要O(1) 额外空间。

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

/// in-place 32-bit recursive radix sort
void I32R(int32_t *data, uint32_t size, uint32_t nbit) {
    uint32_t dbgn = (uint32_t)-1, dend = size;

    while (++dbgn < dend)
        if (data[dbgn] & nbit)
            while (dbgn < --dend)
                if (~data[dend] & nbit) {
                    data[dbgn] ^= data[dend];
                    data[dend] ^= data[dbgn];
                    data[dbgn] ^= data[dend];
                    break;
                }
    if ((nbit >>= 1) && (dend > 1))
        I32R(data, dend, nbit);
    if (nbit && (size - dend > 1))
        I32R(data + dend, size - dend, nbit);
}

/// O_t(n) / O_s(1) duplicate remover
int32_t dups(int32_t *data, uint32_t size) {
    int32_t iter, *uniq = data;

    if (size < 2)
        return size;
    for (iter = 0; iter < size; iter++)
        data[iter] ^= (1 << 31);
    I32R(data, size, 1 << 31);
    data[0] ^= (1 << 31);
    for (iter = 1; iter < size; iter++)
        if (*uniq != (data[iter] ^= (1 << 31)))
            *++uniq = data[iter];
    return uniq - data + 1;
}

void parr(int32_t *data, uint32_t size) {
    for (; size; size--)
        printf("%4d%s", *data++, (size == 1)? "\n\n" : ", ");
}

int main() {
    int32_t iter, size, *data;

    data = malloc((size = 256) * sizeof(*data));
    for (iter = 0; iter < size; iter++)
        data[iter] = (int8_t)rand() & -3;
    parr(data, size);
    parr(data, dups(data, size));
    free(data);
    return 0;
}

注意#1:在排序之前反转符号位对于正数变得大于负数是必要的,因为基数排序只对无符号值进行操作。

注意#2:这只是一个粗略的说明,从未真正测试过。

N.B.#3: 哇哦,这实际上比qsort() 快!

注意#4: 现在有一个非递归版本的排序函数;用法几乎相同,除了缺少nbit

void I32NR(int32_t *data, uint32_t size) {
    int32_t mask, head;
    struct {
        uint32_t init, size, nbit, edge;
    } heap[32];

    heap[0].nbit = 32;
    heap[0].size = size;
    heap[0].init = head = 0;
    do {
        size = heap[head].init - 1;
        mask = 1 << ((heap[head].nbit & 0x7F) - 1);
        heap[head].edge = heap[head].size;
        while (++size < heap[head].edge)
            if (data[size] & mask)
                while (size < --heap[head].edge)
                    if (~data[heap[head].edge] & mask) {
                        data[size] ^= data[heap[head].edge];
                        data[heap[head].edge] ^= data[size];
                        data[size] ^= data[heap[head].edge];
                        break;
                    }
        heap[head].nbit = ((heap[head].nbit & 0x7F) - 1)
                        |  (heap[head].nbit & 0x80);
        if ((heap[head].nbit & 0x7F) && (heap[head].edge > 1)) {
            heap[head + 1] = heap[head];
            heap[head + 1].size = heap[head].edge;
            heap[++head].nbit |= 0x80;
            continue;
        }
        do {
            if ((heap[head].nbit & 0x7F)
            &&  (heap[head].size - heap[head].edge > 1)) {
                heap[head + 1] = heap[head];
                heap[head + 1].init = heap[head].edge;
                heap[++head].nbit &= 0x7F;
                break;
            }
            while ((head >= 0) && !(heap[head--].nbit & 0x80));
        } while (head >= 0);
    } while (head >= 0);
}

【讨论】:

  • 太棒了!我将研究就地基数排序。我以前没听说过。
  • @frederick99 这是一个很好的关键字/算法类,它提供了许多可能性。但请记住,这不适用于这种精确形式的问题表述(因为没有任何关于这些元素的边界/位表示大小的信息)。
  • @sascha,根据 OP 帖子中给出的示例,元素被暗示为数字......至少我是这样理解的。
  • @frederick99,它肯定会,但它的渐近复杂度将远远大于线性,只要字符串通常不受长度限制。
  • @frederick99,计算机内存中的每个标准整数都已存储为二进制。所有你需要提取它的第 M 位是 AND 它与 (1
【解决方案2】:

假设ar[i]=j,检查ar[j]是否为负数,如果负数则删除ar[i],否则将元素ar[j]替换为-ar[j]

注意:只有当所有元素都是正数并且元素位于 0&lt;=elements&lt;array_size 内时,这才有效。

    for(int i = 0; i < ar.length; i++) {
       int elem1 = ar[i];
       int elem2 = ar[Math.abs(elem1)];
       if(elem2 >= 0) {
           ar[Math.abs(elem1)] = -elem2;
       }
       else {
           //elem1 already exists in an array. remove elem1 or copy distinct elements to another array
       }
    }

【讨论】:

  • 引发 java.lang.ArrayIndexOutOfBoundsException。
  • 你能把你想做的事情写进去吗?
  • 如果元素不在我已经提到的数组大小范围内,ArrayIndexOutOfBoundException 将会出现。
  • 不,这是因为您将元素设为负数并且不在代码中处理它。我想知道你是否可以修改你的代码并让它工作。
  • 这是一个绝妙的答案! :D
猜你喜欢
  • 1970-01-01
  • 2011-05-14
  • 2018-04-07
  • 2016-07-08
  • 1970-01-01
  • 2012-07-03
相关资源
最近更新 更多