算法:从数组中删除重复整数的有效方法


92

我在接受Microsoft采访时遇到了这个问题。

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

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

一个警告是预期的算法不应要求首先对数组进行排序。并且,当某个元素被删除后,以下元素也必须向前移动。无论如何,在元素向后移动的数组尾部的元素值可以忽略不计。

更新:结果必须在原始数组中返回,并且不应使用辅助数据结构(例如,哈希表)。但是,我认为没有必要保留订单。

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


4
您是否必须保留唯一编号的顺序?
道格拉斯·里德

1
结果是否必须在原始数组中返回?
Douglas Leeder

1
我已经更新了问题。结果应以原始数组形式返回。但是,顺序的顺序无关紧要。
ejel

3
当有人在问题和其他答案上拉皮条后,这真是令人讨厌。请耐心等待,人们会到达那里。
GManNickG

2
为什么不允许散列表?这种限制是没有道理的。
RBarryYoung

Answers:


19

怎么样:

void rmdup(int *array, int length)
{
    int *current , *end = array + length - 1;

    for ( current = array + 1; array < end; array++, current = array + 1 )
    {
        while ( current <= end )
        {
            if ( *current == *array )
            {
                *current = *end--;
            }
            else
            {
                current++;
            }
        }
    }
}

应为O(n ^ 2)或更小。


3
这是简单的解决方案,而且很可能是面试问题所要寻找的。
柯克·布罗德赫斯特

7
他们甚至可能正在检查您是否不沉迷于过早的优化,除非他们也给了您运行时约束!:-)
Trevor Tippins

16
大声笑,尽管绝对可以更快地对数组进行排序并处理排序后的数组。排序应由API提供,并且不宜过早优化。
ziggystar,

2
是不是while(current <= end)而不是while(current <end)?
Shail 2013年

2
为什么这被接受为正确答案?如果不需要保留订单,那么最好不使用合并排序O(nlogn),然后删除O(n)中的重复元素...总复杂度-O(nlogn)比此解决方案好得多。
Pawan 2014年

136

我女友建议的解决方案是合并排序的一种变体。唯一的修改是在合并步骤中,只需忽略重复的值。该解决方案也将是O(n log n)。在这种方法中,排序/重复删除被组合在一起。但是,我不确定这是否有任何区别。


8
很好的建议,但是您需要记账以跟踪每个合并输出的末尾。我实际上只做过一次,是的,在合并时消除重复项可以使它更快。
Mark Ransom

2
目前尚不清楚O(N / 2)多余的空间是否算作问题中禁止的“辅助数据结构”-我不知道此限制是否旨在规定O(1)的额外空间,还是只是为了规定答案不应该取决于大型数据结构的实现。也许标准合并就可以了。但是,如果不是这样,最重要的提示:不要尝试在面试中编写就地合并排序,除非您真的知道自己在做什么。
Steve Jessop

好想法。但这要求其余数据保持原始顺序。
哈迪·冯

4
以下是描述您女友建议的论文:dc-pubs.dbs.uni-leipzig.de/files/…–
Mike B

49

我已经在SO上发布过此文章一次,但由于它非常酷,所以我将在此处进行复制。它使用散列,在适当位置构建类似散列集的内容。保证在腋窝空间中为O(1)(递归是尾部调用),并且通常为O(N)时间复杂度。算法如下:

  1. 采取数组的第一个元素,这将是前哨。
  2. 尽可能对数组的其余部分重新排序,以使每个元素都位于与其哈希相对应的位置。完成此步骤后,将发现重复项。将它们设置为等于前哨。
  3. 将索引等于哈希的所有元素移到数组的开头。
  4. 将除了数组的第一个元素以外的所有等于哨兵的元素移到数组的末尾。
  5. 正确散列的元素和重复元素之间剩下的就是由于冲突而无法放入与它们的哈希相对应的索引中的元素。递归处理这些元素。

如果在散列中没有病理情况,这可以证明是O(N):即使没有重复项,每次递归也会消除大约2/3的元素。每个递归级别为O(n),其中小n是剩余的元素数量。唯一的问题是,在实践中,当重复项很少(即有很多冲突)时,它比快速排序要慢。但是,当有大量重复项时,它的速度非常快。

编辑:在D的当前实现中,hash_t是32位。关于此算法的所有假设均假设在完整的32位空间中,哈希冲突很少(如果有的话)。但是,碰撞可能会在模量空间中频繁发生。但是,对于任何合理大小的数据集,这种假设很可能是正确的。如果密钥小于或等于32位,则它可以是它自己的哈希,这意味着不可能在完整的32位空间中发生冲突。如果更大,您根本无法将它们容纳在32位内存地址空间中,这将成为问题。我假设在D的64位实现中,hash_t将增加到64位,在D实现中,数据集可能更大。此外,如果确实确实存在问题,则可以在每个递归级别上更改哈希函数。

这是D编程语言的实现:

void uniqueInPlace(T)(ref T[] dataIn) {
    uniqueInPlaceImpl(dataIn, 0);
}

void uniqueInPlaceImpl(T)(ref T[] dataIn, size_t start) {
    if(dataIn.length - start < 2)
        return;

    invariant T sentinel = dataIn[start];
    T[] data = dataIn[start + 1..$];

    static hash_t getHash(T elem) {
        static if(is(T == uint) || is(T == int)) {
            return cast(hash_t) elem;
        } else static if(__traits(compiles, elem.toHash)) {
            return elem.toHash;
        } else {
            static auto ti = typeid(typeof(elem));
            return ti.getHash(&elem);
        }
    }

    for(size_t index = 0; index < data.length;) {
        if(data[index] == sentinel) {
            index++;
            continue;
        }

        auto hash = getHash(data[index]) % data.length;
        if(index == hash) {
            index++;
            continue;
        }

        if(data[index] == data[hash]) {
            data[index] = sentinel;
            index++;
            continue;
        }

        if(data[hash] == sentinel) {
            swap(data[hash], data[index]);
            index++;
            continue;
        }

        auto hashHash = getHash(data[hash]) % data.length;
        if(hashHash != hash) {
            swap(data[index], data[hash]);
            if(hash < index)
                index++;
        } else {
            index++;
        }
    }


    size_t swapPos = 0;
    foreach(i; 0..data.length) {
        if(data[i] != sentinel && i == getHash(data[i]) % data.length) {
            swap(data[i], data[swapPos++]);
        }
    }

    size_t sentinelPos = data.length;
    for(size_t i = swapPos; i < sentinelPos;) {
        if(data[i] == sentinel) {
            swap(data[i], data[--sentinelPos]);
        } else {
            i++;
        }
    }

    dataIn = dataIn[0..sentinelPos + start + 1];
    uniqueInPlaceImpl(dataIn, start + swapPos + 1);
}

1
非常酷,被低估的答案!我喜欢将位置1中的元素用作前哨值的想法。如果我可以提出一些小建议,那就应该更改步骤2,使其包括“每个元素的位置对应于其哈希值以数组大小为模数 ”,并可能澄清要设置为前哨的重复项是具有相同值的元素(与相同的散列或相同的散列取模数组大小相反)。
j_random_hacker 2012年

20

一种更有效的实施

int i, j;

/* new length of modified array */
int NewLength = 1;

for(i=1; i< Length; i++){

   for(j=0; j< NewLength ; j++)
   {

      if(array[i] == array[j])
      break;
   }

   /* if none of the values in index[0..j] of array is not same as array[i],
      then copy the current value to corresponding new position in array */

  if (j==NewLength )
      array[NewLength++] = array[i];
}

在此实现中,无需对数组进行排序。同样,如果找到了重复的元素,则此后无需将所有元素移动一个位置。

该代码的输出是大小为NewLength的array []

在这里,我们从数组中的第二个元素开始,并将其与数组中的所有元素进行比较,直到该数组。我们拥有一个额外的索引变量“ NewLength”,用于修改输入数组。NewLength variabel初始化为0。

array [1]中的元素将与array [0]比较。如果它们不同,则将使用array [1]修改array [NewLength]中的值并递增NewLength。如果它们相同,则不会修改NewLength。

因此,如果我们有一个数组[1 2 1 3 1],则

在“ j”循环的第一遍中,将array [1](2)与array0比较,然后将2写入array [NewLength] = array [1],因为NewLength = 2,因此array将为[1 2]

在“ j”循环的第二遍中,将array [2](1)与array0和array1比较。由于array [2](1)和array0是同一循环,因此这里会中断。因为NewLength = 2,所以数组将为[1 2]

等等


3
好一个。我有一个改善的建议。第二个嵌套循环可以更改为for(j = 0; j <NewLength; j ++),最后一次检查是否可以更改为if(j == NewLength)
Vadakkumpadath,2009年

那是一个很好的建议。我已经根据您的评论更新了代码
Byju 2009年

至少在数组{1,1,1,1,1,1,1}中具有相同值时失败。无用的代码。
Yuriy Chernyshov

那么,这的复杂性是什么,不是O(n ^ 2)吗?
JavaSa 2014年

1
这么多的投票,但是效率不高:当重复次数很少时,它是O(n ^ 2)。
Paul Hankin

19

如果您正在寻找高级的O表示法,则使用O(n log n)排序对数组进行排序,然后进行O(n)遍历可能是最好的方法。如果不进行排序,您将看到O(n ^ 2)。

编辑:如果您只是在做整数,那么您还可以进行基数排序以获得O(n)。


Jeff B的答案仅仅是O(n)。哈希集和哈希字典是蜜蜂的膝盖。
ChrisW

3
ChrisW:如果您假设没有冲突,则哈希集/字典仅为O(1)。(我并不是说我不会用它们解决这个问题,我可能会说,声称它们确实是O(1)只是一个谬论。)
Laurence Gonsalves,2009年

2
实际上,由于您事先知道了数组的大小,因此可以保证O(1)。然后,您可以权衡冲突与使用多少额外内存。
维塔利

您可能需要重新考虑一下否决权-问题的新发布条件使Jeff B的解决方案无效。
Mark Ransom

3
您可能需要详细说明“遍历”,因为幼稚的擦除方法可能会导致大量重复项的O(n ^ 2)。
Mark Ransom

11

1.以O(n log n)的时间使用O(1)额外空间

例如,这是可能的:

  • 首先执行就地O(n log n)排序
  • 然后遍历列表一次,将每个实例的第一个实例写回到列表的开头

我相信ejel的合作伙伴是正确的,做到这一点的最佳方法是采用简化的合并步骤进行就地合并排序,如果您是例如,那可能就是这个问题的意图。编写新的库函数来尽可能高效地执行此操作,而又不能改善输入,并且在某些情况下,根据输入的种类,没有哈希表可能会很有用。但是我实际上还没有检查过。

2.以O(n)的时间使用O(lots)多余的空间

  • 声明一个零数组,其大小足以容纳所有整数
  • 遍历数组一次
  • 将每个整数的对应数组元素设置为1。
  • 如果已经为1,则跳过该整数。

这仅在以下几个可疑假设成立的情况下有效:

  • 可能便宜地将内存归零,或者int的大小比它们的数量小
  • 您很高兴向您的操作系统请求256 ^ sizepof(int)的内存
  • 如果它巨大,它将非常有效地为您缓存

这是一个不好的答案,但是如果您有很多输入元素,但是它们都是8位整数(甚至可能是16位整数),那可能是最好的方法。

3. O(n)-ish时间的多余空间

与#2一样,但使用哈希表。

4.明确的方法

如果元素的数量很少,则如果其他代码的编写速度更快且阅读速度更快,则编写适当的算法将无用。

例如。遍历数组中的每个唯一元素(即第一个元素,第二个元素(第一个元素的重复项已被删除)等),然后删除所有相同的元素。O(1)额外空间,O(n ^ 2)次。

例如。使用执行此操作的库函数。效率取决于您容易获得的资源。


7

好吧,它的基本实现非常简单。遍历所有元素,检查其余元素中是否有重复项,然后将其余部分移到它们上面。

它的效率很低,您可以通过一个辅助数组来加快输出或排序/二叉树的速度,但这似乎是不允许的。


1
OTOH,实现排序树所需的附加代码可能比简单的解决方案效率(内存)低,并且对于小型(例如少于100个元素)数组在运行时可能效率较低。
TMN 2010年

6

如果允许您使用C ++,则先调用,std::sort然后再调用,std::unique将为您提供答案。对于排序,时间复杂度为O(N log N),对于唯一遍历,时间复杂度为O(N)。

而且如果C ++不在市场上,那么没有什么可以阻止这些相同的算法用C编写。


“一个警告是期望的算法不应要求首先对数组进行排序。”
sbi

2
它并不表示一旦获得数组就无法对它进行排序...不使用O(N)外部存储器排序是在O(N log N)或更好的条件下进行排序的唯一方法。
格雷格·罗杰斯

出于此问题的目的,不应使用标准库实用程序。但是,关于排序,我越想它,就越不确定我是否可以。
ejel

1
我认为引用C ++和C ++标准函数的答案很有用,即使它们没有回答原始问题,也可以为以后找到该问题的人提供更全面的答案。
道格拉斯·里德

6

如果您愿意牺牲内存,则可以一次遍历。您可以简单地计算是否在哈希/关联数组中看到整数。如果您已经看过一个数字,则可以随时删除它,或者更好的是,将尚未看到的数字移到新数组中,避免原始数组中的任何移位。

在Perl中:

foreach $i (@myary) {
    if(!defined $seen{$i}) {
        $seen{$i} = 1;
        push @newary, $i;
    }
}

目前尚不清楚答案是否必须在原始数组中。
道格拉斯·里德

为此,不需要新数组,您可以简单地将重复项替换为从数组末尾弹出的元素,然后重做当前循环,因为问题并不指定顺序。这需要一些额外的边界检查,但是非常可行。
杰夫·B

6
直到对问题进行编辑之前,这是一个好主意。您的哈希表想法显然违反了规则。
WCWedin

14
我不明白为什么这个答案被投票得最多。正如问题所问,它是用perl编写的,并使用了C中没有的重要功能。
LiraNuna

5
该问题要求使用C代码,而不是Perl。使用perl可以免费获得哈希表和“推送”。如果我可以在Scala中做到这一点,您只需致电input.removeDuplicates,但我怀疑面试官是否可以接受:)
Peter Recore 09年

5

函数的返回值应该是唯一元素的数量,并且它们都存储在数组的前面。没有这些附加信息,您甚至不会知道是否有重复项。

外循环的每次迭代都会处理数组的一个元素。如果它是唯一的,则它将保留在数组的前面;如果它是重复的,则将被数组中最后一个未处理的元素覆盖。该解决方案运行时间为O(n ^ 2)。

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

size_t rmdup(int *arr, size_t len)
{
  size_t prev = 0;
  size_t curr = 1;
  size_t last = len - 1;
  while (curr <= last) {
    for (prev = 0; prev < curr && arr[curr] != arr[prev]; ++prev);
    if (prev == curr) {
      ++curr;
    } else {
      arr[curr] = arr[last];
      --last;
    }
  }
  return curr;
}

void print_array(int *arr, size_t len)
{
  printf("{");
  size_t curr = 0;
  for (curr = 0; curr < len; ++curr) {
    if (curr > 0) printf(", ");
    printf("%d", arr[curr]);
  }
  printf("}");
}

int main()
{
  int arr[] = {4, 8, 4, 1, 1, 2, 9};
  printf("Before: ");
  size_t len = sizeof (arr) / sizeof (arr[0]);
  print_array(arr, len);
  len = rmdup(arr, len);
  printf("\nAfter: ");
  print_array(arr, len);
  printf("\n");
  return 0;
}

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,1} {0,0,0,0,0,0,1,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

显然,数组应该从右到左“遍历”,以避免不必要的来回复制值。

如果您有无限的内存,则可以为sizeof(type-of-element-in-array) / 8字节分配一个位数组,以使每个位表示您是否已经遇到了相应的值。

如果您不这样做,那么,除了遍历数组并将每个值与它后面的值进行比较,然后再找到重复的值时,我想不出什么更好的方法。这在O(n ^ 2)(或O((n ^ 2-n)/ 2)附近)附近。

IBM上有一篇关于一个很接近的主题的文章


确实-寻找最大元素的O(n)传递不会增加总O()成本。
道格拉斯·里德

2

让我们来看看:

  • O(N)通过以找到最小/最大分配
  • 找到的位数组
  • O(N)通过交换重复到结束。

鉴于它们只是整数,为简单起见,您可以假设32位,而不必费心寻找最小值/最大值:2 ^ 32位“仅”为512MB,因此查找界限只是内存使用和O(1)时间优化(在给出的示例中,已获得了极大的优化)。而且,如果它们是64位的,那么这无关紧要,因为您不知道min和max不会比您拥有的内存位数更远。
Steve Jessop

除了理论之外,分配512MB所花费的时间不会比找到最小值/最大值多吗?
LiraNuna

取决于有多少数据以及最小值/最大值是多少。如果您正在查看超过512MB的输入,那么避免多余的O(N)传递可能会更快。当然,如果您正在查看这么多的输入,那么您有512MB的空闲空间的可能性就较小。如果最小值/最大值接近0 / INT_MAX,则优化也无济于事。我只是说,尽管第一步显然可以帮助小数,但它不能避免在最坏的情况下该算法使用UINT_MAX位的事实,因此您需要为该限制做计划。
史蒂夫·杰索普

您可能是正确的-在任何情况下,对问题的澄清都意味着使用位数组。我将保留此答案,以防以后有人出现不受限制的情况并想查看所有可能的答案。
Douglas Leeder

2

可以使用O(N log N)算法一次性完成此操作,而无需额外的存储空间。

从元素a[1]转到a[N]。在每个阶段中i,所有元素的左边的a[i]包括排序元件堆a[0]通过a[j]。同时,第二个索引j(最初为0)跟踪堆的大小。

检查a[i]并将其插入到堆,它现在占据的元素a[0]a[j+1]。插入元素时,如果a[k]遇到具有相同值的重复元素,则不要将其插入a[i]堆(即丢弃它);否则,将其插入到堆中,该堆现在增加一个元素,现在包括a[0]to a[j+1]和增量j

以这种方式继续,递增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;
}

查看示例,这并不是所要求的,因为结果数组保留了原始元素的顺序。但是如果这个要求放松了,上面的算法就可以解决问题。


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

我认为,换档是其中的一部分,但是您当然是对的。
多米尼克2009年

1
@mocj:我喜欢您的解决方案,看起来非常优雅。但是我认为如果后两个元素相等,那是行不通的,因为您停止在最后一个元素之前检查是否相等。(进入此处是因为太有名望,无法在其他任何地方发表评论:()
多米尼克2009年

您是对的,除了原始问题指出数组末尾的值可以忽略不计。由于您没有返回修改后的数组的长度,因此当两个值相等时,最后一个值和倒数第二个之间的区别并不重要。调用方在哪里将返回数组的末尾解释为
mocj

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

审查问题后,这是我的delphi方式,可能会有所帮助

var
A: Array of Integer;
I,J,C,K, P: Integer;
begin
C:=10;
SetLength(A,10);
A[0]:=1; A[1]:=4; A[2]:=2; A[3]:=6; A[4]:=3; A[5]:=4;
A[6]:=3; A[7]:=4; A[8]:=2; A[9]:=5;

for I := 0 to C-1 do
begin
  for J := I+1 to C-1 do
    if A[I]=A[J] then
    begin
      for K := C-1 Downto J do
        if A[J]<>A[k] then
        begin
          P:=A[K];
          A[K]:=0;
          A[J]:=P;
          C:=K;
          break;
        end
        else
        begin
          A[K]:=0;
          C:=K;
        end;
    end;
end;

//tructate array
setlength(A,C);
end;

1

以下示例应解决您的问题:

def check_dump(x):
   if not x in t:
      t.append(x)
      return True

t=[]

output = filter(check_dump, input)

print(output)
True

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 No.因为“ <arr.length-1”
GabrielBB

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;
}

0

可以单次执行此操作,以O(N)时间以输入列表中整数的数量完成,以O(N)存储以唯一整数的数量完成。

从头到尾遍历列表,将两个指针“ dst”和“ src”初始化为第一项。从“看到的整数”的空哈希表开始。如果哈希中不存在src处的整数,则将其写入dst处的插槽中并递增dst。将src处的整数添加到哈希中,然后递增src。重复直到src通过输入列表的末尾。


2
在对原始问题的修改中,不允许使用哈希表。但是,一旦确定了重复项,您的双指针方法是压缩输出的一种好方法。
Mark Ransom

0

将所有元素插入binary tree the disregards duplicates- O(nlog(n))。然后通过遍历-将它们全部提取回数组中O(n)。我假设您不需要订单保存。



0

在JAVA中,

    Integer[] arrayInteger = {1,2,3,4,3,2,4,6,7,8,9,9,10};

    String value ="";

    for(Integer i:arrayInteger)
    {
        if(!value.contains(Integer.toString(i))){
            value +=Integer.toString(i)+",";
        }

    }

    String[] arraySplitToString = value.split(",");
    Integer[] arrayIntResult = new Integer[arraySplitToString.length];
    for(int i = 0 ; i < arraySplitToString.length ; i++){
        arrayIntResult[i] = Integer.parseInt(arraySplitToString[i]);
    }

输出:{1,2,3,4,6,7,8,9,10}

希望这会有所帮助


1
使用输入进行测试arrayInteger = {100,10,1};
Blastfurnace 2012年


0

首先,您应该创建一个数组check[n],其中n是您要使其不重复的数组的元素数,并将(检查数组的)每个元素的值都设置为等于1。使用for循环将数组与重复,说它的名字是arr,在for循环中这样写:

{
    if (check[arr[i]] != 1) {
        arr[i] = 0;
    }
    else {
        check[arr[i]] = 0;
    }
}

这样,您将每个重复项都设置为零。因此,剩下要做的就是遍历arr数组并打印不等于零的所有内容。订单保持不变,并花费线性时间(3 * n)。


这个问题不允许使用额外的数据结构。
ejel 2014年

0

给定一个由n个元素组成的数组,编写一种算法,以在O(nlogn)的时间内删除数组中的所有重复项

Algorithm delete_duplicates (a[1....n])
//Remove duplicates from the given array 
//input parameters :a[1:n], an array of n elements.

{

temp[1:n]; //an array of n elements. 

temp[i]=a[i];for i=1 to n

 temp[i].value=a[i]

temp[i].key=i

 //based on 'value' sort the array temp.

//based on 'value' delete duplicate elements from temp.

//based on 'key' sort the array temp.//construct an array p using temp.

 p[i]=temp[i]value

  return p.

在其他元素中,使用“键”在输出数组中维护元素。考虑到键的长度为O(n),对键和值进行排序所花费的时间为O(nlogn)。因此,从阵列中删除所有重复项所花费的时间为O(nlogn)。


对于所有粗体字形,您都做了helper data structure (e.g. hashtable) should not be used什么?
灰胡子

不一定需要。我只是强调这些目的是为了理解。
Sharief Muzammil,2015年

0

这就是我所拥有的,尽管它放错了我们可以升序或降序排序的顺序以进行修复。

#include <stdio.h>
int main(void){
int x,n,myvar=0;
printf("Enter a number: \t");
scanf("%d",&n);
int arr[n],changedarr[n];

for(x=0;x<n;x++){
    printf("Enter a number for array[%d]: ",x);
    scanf("%d",&arr[x]);
}
printf("\nOriginal Number in an array\n");
for(x=0;x<n;x++){
    printf("%d\t",arr[x]);
}

int i=0,j=0;
// printf("i\tj\tarr\tchanged\n");

for (int i = 0; i < n; i++)
{
    // printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
    for (int j = 0; j <n; j++)
    {   
        if (i==j)
        {
            continue;

        }
        else if(arr[i]==arr[j]){
            changedarr[j]=0;

        }
        else{
            changedarr[i]=arr[i];

        }
    // printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
    }
    myvar+=1;
}
// printf("\n\nmyvar=%d\n",myvar);
int count=0;
printf("\nThe unique items:\n");
for (int i = 0; i < myvar; i++)
{
        if(changedarr[i]!=0){
            count+=1;
            printf("%d\t",changedarr[i]);   
        }
}
    printf("\n");
}

-1

如果您有一个好的DataStructure可以快速判断它是否包含整数,那将是很酷的。也许是某种树。

DataStructure elementsSeen = new DataStructure();
int elementsRemoved = 0;
for(int i=0;i<array.Length;i++){
  if(elementsSeen.Contains(array[i])
    elementsRemoved++;
  else
    array[i-elementsRemoved] = array[i];
}
array.Length = array.Length - elementsRemoved;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.