算法:从数组中高效地移除重复的整数

93

这个问题来自于微软的面试。

给定一个随机整数数组, 请用 C 语言编写一个算法,去除重复的数字并返回原始数组中的唯一数字。

例如,输入:{4, 8, 4, 1, 1, 2, 9} 输出:{4, 8, 1, 2, 9, ?, ?}

其中一个注意事项是,期望的算法不应该要求先对数组进行排序。当一个元素被移除后,后续的元素必须向前移位。无论如何,被移位的末尾元素的值都是可以忽略的。

更新:结果必须返回到原始数组中,不能使用帮助数据结构(例如哈希表)。然而,我猜想保持元素顺序并不是必需的。

更新2:对于那些想知道为什么有这些不切实际的限制的人,这是一个面试题,所有这些限制都是在思考过程中讨论的,以了解我如何提出不同的想法。


4
你需要保持独特数字的顺序吗? - Douglas Leeder
1
结果必须返回到原始数组中吗? - Douglas Leeder
1
我已经更新了问题。结果应该以原始数组的形式返回。然而,序列的顺序并不重要。 - ejel
3
当有人在问题和其他答案上进行推销时,这是相当让人烦恼的。只要耐心等待,人们最终会有结果的。 - GManNickG
3
为什么不允许使用哈希表?这个限制毫无意义。 - RBarryYoung
显示剩余4条评论
34个回答

4

这是一个Java版本。

int[] removeDuplicate(int[] input){

        int arrayLen = input.length;
        for(int i=0;i<arrayLen;i++){
            for(int j = i+1; j< arrayLen ; j++){
                if(((input[i]^input[j]) == 0)){
                    input[j] = 0;
                }
                if((input[j]==0) && j<arrayLen-1){
                        input[j] = input[j+1];
                        input[j+1] = 0;
                    }               
            }
        }       
        return input;       
    }

至少在以下输入时失败: {1,1,1,1,1,1,1} {0,0,0,0,0,1,1,1,1,1,1} - Yuriy Chernyshov

3
这是我的解决方案。
///// find duplicates in an array and remove them

void unique(int* input, int n)
{
     merge_sort(input, 0, n) ;

     int prev = 0  ;

     for(int i = 1 ; i < n ; i++)
     {
          if(input[i] != input[prev])
               if(prev < i-1)
                   input[prev++] = input[i] ;                         
     }
}

2

这可以通过一次O(N log N)的算法完成,而且不需要额外的存储空间。

从元素 a[1] 开始,遍历到 a[N]。在每个阶段 i,左侧所有元素都包括一个由元素 a[0]a[j] 的排序堆。同时,第二个索引 j,初始值为0,用于跟踪堆的大小。

检查 a[i] 并将其插入堆中,堆现在占据着元素 a[0]a[j+1]。当元素被插入时,如果遇到具有相同值的重复元素 a[k],则不要将 a[i] 插入堆中(即放弃它);否则将其插入堆中,堆现在增加了一个元素,包含 a[0]a[j+1] 的全部元素,并且将 j 增加1。

继续以这种方式递增 i,直到检查并将所有数组元素插入堆中,堆最终占用 a[0]a[j]j 是堆的最后一个元素的索引,堆仅包含唯一的元素值。

int algorithm(int[] a, int n)
{
    int   i, j;  

    for (j = 0, i = 1;  i < n;  i++)
    {
        // Insert a[i] into the heap a[0...j]
        if (heapInsert(a, j, a[i]))
            j++;
    }
    return j;
}  

bool heapInsert(a[], int n, int val)
{
    // Insert val into heap a[0...n]
    ...code omitted for brevity...
    if (duplicate element a[k] == val)
        return false;
    a[k] = val;
    return true;
}

查看此示例,这并不完全符合所要求的,因为生成的数组保留了原始元素顺序。但是,如果放宽这个要求,上面的算法应该可以解决问题。


2
显然,数组应该从右到左“遍历”,以避免不必要的值来回复制。如果您拥有无限的内存,可以为每个元素类型分配一个位数组sizeof(type-of-element-in-array) / 8字节,以表示是否已经遇到相应的值。如果没有,我想不出比遍历数组并将每个值与其后面的值进行比较更好的方法,然后如果发现重复,则完全删除这些值。这大约是O(n^2)(或O((n^2-n)/2))。IBM有一篇关于类似主题的文章

实际上,使用O(n)的方法来查找最大元素不会增加整体的O()成本。 - Douglas Leeder

2

让我们来看看:

  • O(N)遍历以查找最小/最大值并分配内存
  • 使用比特数组进行查找
  • O(N)遍历将重复项交换到末尾。

考虑到它们只是整数,为了简单起见,您可以假设32位并不费心寻找最小/最大值:2^32位仅为“仅有”的512MB,因此找到边界只是一种内存使用和O(1)时间优化(当然,在给定示例的情况下,这是一个繁重的优化)。如果它们是64位,则无关紧要,因为您不知道最小值和最大值是否会比您拥有的内存位数更远。 - Steve Jessop
理论上,分配512MB的时间比查找最小/最大值的时间更长,不是吗? - LiraNuna
取决于数据量大小和最小/最大值。如果输入超过512MB,那么避免额外的O(N)遍历可能会更快。当然,如果你处理这么多数据,那么你很少有512MB的空余内存。如果最小/最大值接近0/INT_MAX,那么优化也不会有太大帮助。我只是想说,虽然第一步显然对小数字有帮助,但它无法避免该算法在最坏情况下使用UINT_MAX位,因此你需要计划好这个限制。 - Steve Jessop
你说得很有道理 - 无论如何,问题的澄清意味着不能使用位数组。我会保留这个答案,以防后来有人没有限制条件并想查看所有可能的答案。 - Douglas Leeder

1
在Java中,我会这样解决它。不知道如何用C写这个。
   int length = array.length;
   for (int i = 0; i < length; i++) 
   {
      for (int j = i + 1; j < length; j++) 
      {
         if (array[i] == array[j]) 
         {
            int k, j;
            for (k = j + 1, l = j; k < length; k++, l++) 
            {
               if (array[k] != array[i]) 
               {
                  array[l] = array[k];
               }
               else
               {
                  l--;
               }
            }
            length = l;
         }
      }
   }

如果您用数组末尾的值覆盖您发现的重复值,就可以避免内部for()循环中整个数组的移位。 这将使您从O(n ^ 3)降至O(n ^ 2)。我的C实现程序在这里漂泊...... - mocj
我以为移位是要求的一部分,但你当然是正确的。 - Dominik
1
@mocj:我喜欢你的解决方案,看起来非常优雅。但是我认为如果最后两个元素相等,它就不起作用,因为您在倒数第二个之前停止检查相等性。(在这里发表评论,因为我在其他地方没有足够的声望来评论:( ) - Dominik
你说得没错,除了原问题说明数组末尾的值是可以忽略不计的。由于你没有返回修改后的数组长度,当最后两个值相等时,最后一个值和倒数第二个值之间的区别就不重要了。调用者在哪里解释返回的数组结束呢? - mocj

1
import java.util.ArrayList;


public class C {

    public static void main(String[] args) {

        int arr[] = {2,5,5,5,9,11,11,23,34,34,34,45,45};

        ArrayList<Integer> arr1 = new ArrayList<Integer>();

        for(int i=0;i<arr.length-1;i++){

            if(arr[i] == arr[i+1]){
                arr[i] = 99999;
            }
        }

        for(int i=0;i<arr.length;i++){
            if(arr[i] != 99999){

                arr1.add(arr[i]);
            }
        }

        System.out.println(arr1);
}
    }

arr[i+1] 在最后一个元素时应该抛出 ArrayIndexOutOfBoundsException 吗? - Sathesh
@Sathesh 不行。因为 "< arr.length-1" 的缘故。 - GabrielBB

1

以下方案如何?

int* temp = malloc(sizeof(int)*len);
int count = 0;
int x =0;
int y =0;
for(x=0;x<len;x++)
{
    for(y=0;y<count;y++)
    {
        if(*(temp+y)==*(array+x))
        {
            break;
        }
    }
    if(y==count)
    {
        *(temp+count) = *(array+x);
        count++;
    }
}
memcpy(array, temp, sizeof(int)*len);

我尝试声明一个临时数组,并将元素放入其中,然后再将所有内容复制回原始数组。


1
这是朴素的(N *(N-1)/ 2)解决方案。它使用恒定的额外空间并保持原始顺序。它类似于@Byju的解决方案,但不使用if(){}块。它还避免将元素复制到自身。
#include <stdio.h>
#include <stdlib.h>

int numbers[] = {4, 8, 4, 1, 1, 2, 9};
#define COUNT (sizeof numbers / sizeof numbers[0])

size_t undup_it(int array[], size_t len)
{
size_t src,dst;

  /* an array of size=1 cannot contain duplicate values */
if (len <2) return len; 
  /* an array of size>1 will cannot at least one unique value */
for (src=dst=1; src < len; src++) {
        size_t cur;
        for (cur=0; cur < dst; cur++ ) {
                if (array[cur] == array[src]) break;
                }
        if (cur != dst) continue; /* found a duplicate */

                /* array[src] must be new: add it to the list of non-duplicates */
        if (dst < src) array[dst] = array[src]; /* avoid copy-to-self */
        dst++;
        }
return dst; /* number of valid alements in new array */
}

void print_it(int array[], size_t len)
{
size_t idx;

for (idx=0; idx < len; idx++)  {
        printf("%c %d", (idx) ? ',' :'{' , array[idx] );
        }
printf("}\n" );
}

int main(void) {    
    size_t cnt = COUNT;

    printf("Before undup:" );    
    print_it(numbers, cnt);    

    cnt = undup_it(numbers,cnt);

    printf("After undup:" );    
    print_it(numbers, cnt);

    return 0;
}

1
以下示例应该可以解决您的问题:
def check_dump(x):
   if not x in t:
      t.append(x)
      return True

t=[]

output = filter(check_dump, input)

print(output)
True

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接