Algoritma: bir diziden yinelenen tam sayıları kaldırmanın etkili yolu


93

Bu sorunu Microsoft ile yaptığım bir röportajdan aldım.

Rastgele bir tamsayı dizisi verildiğinde, C'ye yinelenen sayıları kaldıran ve orijinal dizideki benzersiz sayıları döndüren bir algoritma yazın.

Örneğin Giriş: {4, 8, 4, 1, 1, 2, 9} Çıkış:{4, 8, 1, 2, 9, ?, ?}

Bir uyarı, beklenen algoritmanın dizinin önce sıralanmasını gerektirmemesidir. Ve bir öğe kaldırıldığında, aşağıdaki öğeler de ileriye kaydırılmalıdır. Her neyse, öğelerin ileri kaydırıldığı dizinin sonundaki öğelerin değeri ihmal edilebilir.

Güncelleme: Sonuç orijinal dizide döndürülmeli ve yardımcı veri yapısı (örn. Hashtable) kullanılmamalıdır. Ancak sanırım emrin korunması gerekli değil.

Güncelleme2: Bu pratik olmayan kısıtlamaların nedenini merak edenler için, bu bir mülakat sorusuydu ve tüm bu kısıtlamalar, farklı fikirleri nasıl ortaya çıkarabileceğimi görmek için düşünme sürecinde tartışılıyor.


4
Benzersiz numaraların sırasını korumanız gerekiyor mu?
Douglas Leeder

1
Sonuç orijinal dizide döndürülmek zorunda mı?
Douglas Leeder

1
Soruyu güncelledim. Sonuç orijinal dizide döndürülmelidir. Bununla birlikte, dizinin sırası önemli değildir.
ejel

3
Birinin soruya ve diğer cevaplara verdiği cevabı pezevenkleştirmesi oldukça can sıkıcı. Sabırlı olun, insanlar oraya ulaşacak.
GManNickG

2
Bir hashtable'a neden izin verilmiyor? Bu kısıtlama mantıklı değil.
RBarryYoung

Yanıtlar:


20

Peki ya:

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) veya daha küçük olmalıdır.


3
Bu basit bir çözümdür ve muhtemelen röportaj sorusunun aradığı şeydir.
Kirk Broadhurst

8
Size çalışma zamanı kısıtlamaları da vermedikleri sürece, erken optimizasyondan muzdarip olmadığınızı bile kontrol ediyor olabilirler! :-)
Trevor Tippins

16
Lol, ancak diziyi sıralamak ve sıralanan dizide çalışmak kesinlikle daha hızlı. Sıralama bir API tarafından sağlanmalıdır ve erken optimizasyona gerek yoktur.
ziggystar

2
While (current <end) yerine while (current <= end) olması gerekmez mi?
Shail

2
Bu neden doğru cevap olarak kabul edildi? Sıranın korunması gerekli değilse, o zaman sadece birleştirme sıralaması O (nlogn) kullanmak ve daha sonra bu çözümden çok daha iyi olan O (n) ... toplam karmaşıklık - O (nlogn) içindeki tekrarlanan öğeleri kaldırmak daha iyi değildir.
Pawan

136

Kız arkadaşımın önerdiği bir çözüm, birleştirme türünün bir çeşididir. Tek değişiklik, birleştirme adımı sırasında, yinelenen değerleri göz ardı etmesidir. Bu çözüm aynı zamanda O (n log n) olacaktır. Bu yaklaşımda, ayırma / çoğaltma kaldırma birlikte birleştirilir. Ancak bunun bir fark yaratıp yaratmayacağından emin değilim.


8
Harika bir öneri, ancak her birleştirme çıktısının sonunu takip etmek için bazı hesap defterlerine ihtiyacınız olacak. Aslında bunu bir kez yaptım ve evet, siz birleştirirken kopyaları ortadan kaldırmak çok daha hızlı hale getiriyor.
Mark Ransom

2
O (N / 2) fazladan boşluğun soruda yasaklanan "yardımcı veri yapısı" olarak sayılıp sayılmayacağı açık değil - Kısıtlamanın O (1) ekstra alanı mı yoksa sadece yanıt, büyük bir veri yapısı uygulamasına bağlı olmamalıdır. Belki standart bir birleştirme iyidir. Ama değilse, en iyi ipucu: Ne yaptığınızı gerçekten bilmiyorsanız , bir röportajda yerinde birleştirme sıralaması yazmaya çalışmayın .
Steve Jessop

İyi fikir. Ancak kalan verilerin orijinal sırayı korumasını gerektirir.
Hardy Feng

4
Kız arkadaşınızın ne önerdiğini anlatan bir makale şöyledir: dc-pubs.dbs.uni-leipzig.de/files/…
Mike B

50

Bunu daha önce bir kez SO'da yayınladım, ancak burada yeniden üreteceğim çünkü oldukça havalı. Yerleşik bir hash gibi bir şey inşa ederek hashing kullanır. Koltuk altı uzayda O (1) olması garantilidir (özyineleme bir kuyruk çağrısıdır) ve tipik olarak O (N) zaman karmaşıklığıdır. Algoritma aşağıdaki gibidir:

  1. Dizinin ilk elemanını alın, bu nöbetçi olacaktır.
  2. Dizinin geri kalanını mümkün olduğunca yeniden sıralayın, her öğe kendi hash değerine karşılık gelen konumda olsun. Bu adım tamamlandığında, kopyalar keşfedilecektir. Onları nöbetçiye eşit ayarlayın.
  3. Dizinin karma değerine eşit olduğu tüm öğeleri dizinin başlangıcına taşı.
  4. Dizinin ilk öğesi dışında sentinele eşit olan tüm öğeleri dizinin sonuna taşı.
  5. Doğru şekilde hash edilmiş öğeler ile yinelenen öğeler arasında kalan, bir çarpışma nedeniyle hash'lerine karşılık gelen dizine yerleştirilemeyen öğeler olacaktır. Bu unsurlarla başa çıkmak için tekrarlayın.

Karma işleminde patolojik bir senaryo olmaması koşuluyla, bunun O (N) olduğu gösterilebilir: Yineleme olmasa bile, öğelerin yaklaşık 2 / 3'ü her özyinelemede ortadan kaldırılacaktır. Her özyineleme seviyesi O (n) 'dir, burada küçük n kalan elemanların miktarıdır. Tek sorun, pratikte, birkaç kopya, yani çok sayıda çarpışma olduğunda hızlı sıralamadan daha yavaş olmasıdır. Bununla birlikte, çok sayıda kopya olduğunda, inanılmaz derecede hızlıdır.

Düzenleme: D'nin mevcut uygulamalarında, hash_t 32 bittir. Bu algoritmayla ilgili her şey, tam 32-bit uzayda çok az karma çarpışma olacağını varsayar. Bununla birlikte, çarpışmalar modül uzayında sıklıkla meydana gelebilir. Ancak bu varsayım, makul büyüklükteki herhangi bir veri kümesi için büyük olasılıkla doğru olacaktır. Anahtar 32 bitten küçükse veya buna eşitse, kendi karması olabilir, yani tam 32 bit alanda bir çarpışma imkansızdır. Daha büyükse, sorun olması için 32 bit bellek adres alanına yeterince sığdıramazsınız. Veri kümelerinin daha büyük olabileceği 64 bitlik D uygulamalarında hash_t'nin 64 bit'e çıkarılacağını varsayıyorum. Dahası, eğer bunun bir sorun olduğu kanıtlanırsa, her özyineleme seviyesinde karma işlevi değiştirilebilir.

İşte D programlama dilinde bir uygulama:

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
Son derece havalı, hafife alınmayan cevap! 1. pozisyondaki elemanı bir sentinel değer olarak kullanma fikrini seviyorum. Birkaç küçük öneride bulunabilseydim, adım 2'yi "her bir öğe, dizi boyutunun hash modülüne karşılık gelen konumda" içerecek şekilde değiştirmek ve belki de sentinele ayarlanacak kopyaların aynı değere sahip öğeler (aynı hash veya aynı hash modulo dizi boyutunun aksine).
j_random_hacker

20

Daha verimli bir uygulama

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

Bu uygulamada dizinin sıralanmasına gerek yoktur. Ayrıca, yinelenen bir öğe bulunursa, bundan sonra tüm öğeleri bir konum kaydırmaya gerek yoktur.

Bu kodun çıktısı NewLength boyutunda [] dizisidir

Burada dizinin 2. elemesinden başlayıp dizideki tüm elemanlarla bu diziye kadar karşılaştırıyoruz. Giriş dizisini değiştirmek için fazladan bir indeks değişkeni 'NewLength' tutuyoruz. NewLength değişken etiketi 0 olarak başlatılır.

[1] dizisindeki öğe, [0] dizisi ile karşılaştırılacaktır. Farklılarsa, [YeniUzunluk] dizisindeki değer [1] dizisi ile değiştirilecek ve YeniUzunluğunu artıracaktır. Aynı iseler, NewLength değiştirilmeyecektir.

Yani bir [1 2 1 3 1] dizimiz varsa, o zaman

'J' döngüsünün ilk geçişinde, dizi [1] (2) dizi0 ile karşılaştırılacak, daha sonra 2, [YeniUzunluk] = dizi [1] dizisine yazılacaktır, böylece NewLength = 2 olduğundan dizi [1 2] olacaktır.

'J' döngüsünün ikinci geçişinde, dizi [2] (1) dizi0 ve dizi1 ile karşılaştırılacaktır. Burada dizi [2] (1) ve dizi0 aynı olduğundan döngü burada kırılacaktır. NewLength = 2 olduğundan dizi [1 2] olacaktır

ve bunun gibi


3
Güzel bir. Geliştirmem gereken bir önerim var. İkinci iç içe döngü için değiştirilebilir (j = 0; j <NewLength; j ++) ve son olarak, eğer kontrol if olarak değiştirilebilirse (j == NewLength)
Vadakkumpadath

Bu harika bir öneriydi. Kodu yorumunuza göre güncelledim
Byju

En azından {1,1,1,1,1,1} dizisinde aynı değerlere sahipsek başarısız olur. Yararsız kod.
Yuriy Chernyshov

Peki bunun karmaşıklığı nedir, o da O (n ^ 2) değil mi?
JavaSa

1
Çok fazla olumlu oy var, ancak bu verimli değil: birkaç kopya olduğunda O (n ^ 2).
Paul Hankin

19

Üstün O-gösterimi arıyorsanız, diziyi O (n log n) sıralamasıyla sıralamak ve ardından bir O (n) geçişi yapmak en iyi yol olabilir. Sıralama yapmadan O (n ^ 2) 'ye bakarsınız.

Düzenleme: Eğer sadece tamsayı yapıyorsanız, O (n) elde etmek için radix sıralaması da yapabilirsiniz.


Jeff B'nin cevabı sadece O (n). Karma kümeler ve karma sözlükler arıların dizleridir.
ChrisW

3
ChrisW: Karma kümeler / sözlükler, çarpışma olmadığını varsayarsanız yalnızca O (1) 'dir. (Bunları bu problem için kullanmayacağımı söylemiyorum - muhtemelen kullanırım - onların gerçekten O (1) olduklarını iddia etmek yanlıştır.)
Laurence Gonsalves

2
Aslında, önceden dizinin boyutunu bildiğiniz için, O (1) garanti edebilirsiniz. Daha sonra, kullandığınız ek bellek ile çarpışmaları arasında değiş tokuş yapabilirsiniz.
Vitali

Bu olumsuz oyu yeniden düşünmek isteyebilirsiniz - soruna yeni gönderilen koşullar Jeff B'nin çözümünü geçersiz kılar.
Mark Ransom

3
Saf bir silme yöntemi çok sayıda yineleme için O (n ^ 2) ile sonuçlanabileceğinden, "geçiş" konusunda daha fazla ayrıntıya girmek isteyebilirsiniz.
Mark Ransom

11

1. O (n log n) zamanında O (1) ekstra boşluk kullanma

Bu mümkündür, örneğin:

  • önce yerinde bir O (n log n) sıralaması yapın
  • sonra listeyi bir kez gözden geçirin ve her geri dönüşün ilk örneğini listenin başına yazın

Ejel'in ortağının haklı olduğuna inanıyorum, bunu yapmanın en iyi yolu, basitleştirilmiş bir birleştirme adımıyla yerinde birleştirme sıralaması olacaktır ve örneğin, eğer öyleyseniz sorunun amacı muhtemelen budur. girdileri iyileştirme yeteneği olmadan bunu mümkün olduğunca verimli bir şekilde yapmak için yeni bir kütüphane işlevi yazmak ve girdilerin türüne bağlı olarak bunu bir karma tablo olmadan yapmak faydalı olacaktır. Ama bunu gerçekten kontrol etmedim.

2. O (n) sürede O (lot) ekstra boşluk kullanma

  • tüm tam sayıları tutacak kadar büyük sıfırlanmış bir dizi bildirir
  • dizide bir kez yürü
  • her tam sayı için karşılık gelen dizi öğesini 1 olarak ayarlayın.
  • Zaten 1 ise, bu tamsayıyı atlayın.

Bu yalnızca birkaç şüpheli varsayım geçerliyse işe yarar:

  • Belleği ucuza sıfırlamak mümkündür veya int'lerin boyutu sayılarına kıyasla küçüktür
  • işletim sisteminizden 256 ^ boyutepof (int) bellek istemekten mutluluk duyarsınız
  • ve eğer devasa boyutlarda ise sizin için gerçekten verimli bir şekilde

Bu kötü bir cevap, ancak ÇOK SAYIDA giriş elemanınız varsa, ancak bunların hepsi 8 bitlik tam sayılar (veya belki 16 bitlik tam sayılar) en iyi yol olabilir.

3. O (küçük) -ish ekstra boşluk, O (n) -ish zaman

# 2 gibi, ancak bir karma tablo kullanın.

4. Açık yol

Öğelerin sayısı azsa, diğer kodun yazılması ve okunması daha hızlıysa uygun bir algoritma yazmak yararlı değildir.

Örneğin. Tüm özdeş öğeleri kaldırarak her benzersiz öğe için (yani birinci öğe, ikinci öğe (kaldırılan ilk öğenin kopyaları) vb.) Dizide gezinin. O (1) ekstra boşluk, O (n ^ 2) zaman.

Örneğin. Bunu yapan kütüphane işlevlerini kullanın. verimlilik, kolayca elde edebileceğinize bağlıdır.


7

Eh, temel uygulaması oldukça basittir. Tüm unsurları gözden geçirin, kalanlarda kopya olup olmadığını kontrol edin ve geri kalanını bunların üzerine kaydırın.

Bu korkunç verimsiz ve çıktı veya sıralama / ikili ağaçlar için bir yardımcı dizi ile hızlandırabilirsiniz, ancak buna izin verilmiyor gibi görünüyor.


1
OTOH, bir sıralama ağacını uygulamak için gereken ek kod, basit çözümden daha az (bellek) verimli olabilir ve küçük (örneğin 100 öğeden az) diziler için çalışma zamanında muhtemelen daha az verimlidir.
TMN

6

C ++ kullanma izniniz varsa, bir çağrı ve std::sortardından bir çağrı std::uniquesize cevabı verecektir. Zaman karmaşıklığı sıralama için O (N log N) ve benzersiz geçiş için O (N) 'dir.

Ve C ++ tablonun dışındaysa, aynı algoritmaların C'de yazılmasını engelleyen hiçbir şey yoktur.


"Bir uyarı, beklenen algoritmanın dizinin önce sıralanmasını gerektirmemesidir."
sbi

2
Diziyi aldıktan sonra sıralayamayacağınızı söylemez ... O (N) kullanmadan harici bellek sıralaması, O (N log N) veya daha iyi bir şekilde yapmanın tek yoludur.
Greg Rogers

Problemin amacı için, standart kütüphane araçları kullanılmamalıdır. Ancak sıralama ile ilgili olarak, onu ne kadar çok düşünürsem, iyi olup olmadığından o kadar emin değilim.
ejel

1
C ++ ve C ++ standart işlevlerine atıfta bulunan yanıtların, daha sonra bu soruyu bulan kişilere daha kapsamlı bir yanıt sağladıkları için, orijinal soruyu yanıtlamasalar bile yararlı olduğunu düşünüyorum.
Douglas Leeder

6

Hafızayı feda etmeye istekliysen, bunu tek seferde yapabilirsin. Bir hash / ilişkisel dizide bir tamsayı görüp görmediğinizi basitçe sayabilirsiniz. Daha önce bir sayı gördüyseniz, giderken çıkarın veya daha iyisi, orijinal dizide herhangi bir kaymadan kaçınarak, görmediğiniz sayıları yeni bir diziye taşıyın.

Perl'de:

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

Cevabın orijinal dizide olması gerekip gerekmediği net değil.
Douglas Leeder

Bunu yeni bir dizi gerektirmeden yapmak için, kopyayı dizinin sonundan fırlatılan bir öğe ile değiştirebilir ve sorun bu sıranın önemli olduğunu belirtmediğinden mevcut döngüyü yeniden yapabilirsiniz. Bu, bazı ekstra sınırların kontrol edilmesini gerektirir, ancak çok yapılabilir.
Jeff B

6
Soru düzenlenene kadar bu iyi bir fikirdi. Hashtable fikriniz görünüşe göre kurallara aykırı.
WCWedin

14
Neden bu cevaba en çok oy verildiğini anlamıyorum. Perl ile yazılmıştır ve sorunun sorduğu gibi C'de bulunmayan hayati özellikleri kullanır.
LiraNuna

5
soru perl değil, c kodu istedi. perl kullanmak size hashtables ve ücretsiz "push" sağlar. Bunu ölçeklendirebilirsem, sadece input.removeDuplicates'i çağırırdınız, ancak bunun görüşmeciler için kabul edilebilir olacağından şüpheliyim :)
Peter Recore

5

İşlevin dönüş değeri, benzersiz öğelerin sayısı olmalıdır ve bunların tümü dizinin önünde saklanır. Bu ek bilgi olmadan, herhangi bir kopya olup olmadığını bile bilemezsiniz.

Dış döngünün her yinelemesi, dizinin bir öğesini işler. Benzersiz ise dizinin önünde kalır ve yinelenen bir öğe ise dizideki son işlenmemiş eleman tarafından üzerine yazılır. Bu çözüm O (n ^ 2) zamanda çalışır.

#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

İşte bir Java Sürümü.

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

En azından sonraki girişlerde başarısız: {1,1,1,1,1,1,1} {0,0,0,0,0,1,1,1,1,1,1}
Yuriy Chernyshov

3

İşte benim çözümüm.

///// 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

Değerlerin gereksiz yere kopyalanmasını önlemek için bir dizi açıkça sağdan sola "geçilmelidir".

Sınırsız belleğiniz varsa, sizeof(type-of-element-in-array) / 8her bitin karşılık gelen değerle zaten karşılaşıp karşılaşmadığınızı belirtmesi için baytlar için bir bit dizisi ayırabilirsiniz .

Bunu yapmazsanız, bir diziyi dolaşmaktan ve her bir değeri onu takip eden değerlerle karşılaştırmaktan daha iyi bir şey düşünemiyorum ve sonra yinelenen değer bulunursa, bu değerleri tamamen kaldırın. Bu, O (n ^ 2) (veya O ((n ^ 2-n) / 2) ) yakınında bir yer.

IBM'in konuya yakın bir makalesi var.


Aslında - en büyük elemanı bulmak için bir O (n) geçmek, toplam O () maliyetini artırmaz.
Douglas Leeder

2

Bakalım:

  • Min / maks tahsisini bulmak için O (N) geçti
  • bulunan bit dizisi
  • O (N) yinelenenleri sona erdirir.

Sadece tamsayı oldukları göz önüne alındığında, basitlik için 32 bit varsayabilir ve min / maks aramaya zahmet etmeyebilirsiniz: 2 ^ 32 bit "yalnızca" 512MB'dir, bu nedenle sınırları bulmak yalnızca bir bellek kullanımı ve O (1) zaman optimizasyonudur (verilen örnek durumunda ağır bir optimizasyon verildi). Ve eğer 64bit iseler, bu alakasızdır çünkü min ve max'ın sahip olduğunuz bellek bitlerinin sayısından daha fazla ayrı olmayacağını bilmiyorsunuz.
Steve Jessop

Teori bir yana, 512MB ayırmak, min / maks bulmaktan daha fazla zaman almaz mı?
LiraNuna

Ne kadar veri olduğuna ve min / maks değerinin ne olduğuna bağlıdır. 512 MB'tan fazla girişe bakıyorsanız, o zaman fazladan O (N) geçişinden kaçınmak büyük olasılıkla daha hızlıdır. Elbette, bu kadar çok girdiye bakıyorsanız, o zaman 512MB yedeklemeniz daha az olasıdır. Min / maks değerinin 0 / INT_MAX'a yakın olduğu durumlarda, optimizasyon da yardımcı olmaz. İlk adımın küçük sayılar için açıkça yardımcı olmasına rağmen, bu algoritmanın en kötü durumda UINT_MAX bitleri kullanması gerçeğinden kaçınamayacağını söylüyorum, bu nedenle bu sınırlamayı planlamanız gerekir.
Steve Jessop

Haklı olabilirsiniz - her halükarda sorunun açıklığa kavuşturulması, bir bit dizisinin kullanılmadığı anlamına gelir. Birisi daha sonra kısıtlamalar olmadan gelir ve tüm olası cevapları görmek isterse bu cevabı bırakacağım.
Douglas Leeder

2

Bu, bir O (N log N) algoritması ile ve ekstra depolama olmadan tek geçişte yapılabilir.

Öğesinden devam a[1]etmek a[N]. Her aşamada i, sol için tüm öğeleri a[i]itibarıyla bir elemanlarının yığın kriteri a[0]ile a[j]. Bu arada, jbaşlangıçta 0 olan ikinci bir dizin , yığının boyutunu izler.

İnceleyin a[i]ve şimdi unsurları işgal yığın, takın a[0]için a[j+1]. Öğe eklendikçe, a[k]aynı değere sahip yinelenen bir öğe ile karşılaşılırsa a[i], yığına eklemeyin (yani, atın); aksi takdirde, şimdi bir eleman kadar büyüyen ve şimdi a[0]to a[j+1]ve artımı içeren yığının içine ekleyin j.

Bu şekilde devam edin, itüm dizi öğeleri incelenip yığının içine yerleştirilene kadar artarak devam edin, bu da onu işgal a[0]eder a[j]. jyığının son öğesinin dizinidir ve yığın yalnızca benzersiz öğe değerlerini içerir.

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

Örneğe bakıldığında, ortaya çıkan dizi orijinal öğe sırasını koruduğu için bu tam olarak istenen şey değildir. Ancak bu gereksinim gevşetilirse, yukarıdaki algoritma hile yapmalıdır.


1

Java'da bu şekilde çözerdim. Bunu C'de nasıl yazacağımı bilmiyorum.

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

Dizinin sonundaki değerle bulduğunuz kopyaların üzerine yazarsanız, iç for () döngüsünüzdeki tüm dizinin kaymasını önleyebilirsiniz. Bu sizi O (n ^ 3) 'ten O (n ^ 2)' ye getirecektir. C uygulamam burada bir yerde yüzüyor ...
mocj

Değişimin şartın bir parçası olduğunu düşündüm, ama haklısın tabii.
Dominik

1
@mocj: Çözümünüzü beğendim, çok şık görünüyor. Ama bence son iki unsur eşitse işe yaramaz çünkü sondan önce eşitliği kontrol etmeyi bırakırsınız. (buraya geliyor çünkü başka bir yerde yorum yapmak için çok fazla itibarınız var :()
Dominik

Haklısın, ancak asıl problem dizinin sonundaki değerlerin ihmal edilebilir olduğunu belirtiyor. Değiştirilmiş dizinin uzunluğunu döndürmediğiniz için, iki değer eşit olduğunda, son değer ile ikinciden sona arasındaki fark önemsizdir. Arayan, döndürülen dizinin sonunu nerede yorumluyor
mocj

1

Aşağıdakilere ne dersiniz?

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

Her şeyi orijinal diziye geri kopyalamadan önce geçici bir dizi tanımlamaya ve öğeleri içine koymaya çalışıyorum.


1

Sorunu gözden geçirdikten sonra, işte benim delphi yolum, bu yardımcı olabilir

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

Aşağıdaki örnek sorununuzu çözmelidir:

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], son eleman için ArrayIndexOutOfBoundsException'ı atmalı mı?
Sathesh

@Sathesh Hayır. "<Arr.length-1"
yüzünden

1

Bu saf (N * (N-1) / 2) çözümdür. Sabit ek alan kullanır ve orijinal düzeni korur. @ Byju'nun çözümüne benzer, ancak if(){}blok kullanmaz. Aynı zamanda bir öğeyi kendi üzerine kopyalamaktan da kaçınır.

#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

Bu tek geçişte, giriş listesindeki tamsayı sayısında O (N) zamanında ve benzersiz tamsayıların sayısında O (N) depolamada yapılabilir.

İlk öğe olarak başlatılan iki işaretçi "dst" ve "src" ile listede önden arkaya doğru yürüyün. Boş bir "görülen tamsayılar" hash tablosu ile başlayın. Eğer src'deki tamsayı karmada yoksa, onu dst'deki yuvaya yazın ve dst'yi artırın. Tamsayıyı src'de hash'e ekleyin, ardından src'yi artırın. Src, giriş listesinin sonunu geçene kadar tekrarlayın.


2
Orijinal soruda yapılan değişiklikte, karma tablolara izin verilmez. Yinelenenleri belirledikten sonra, iki işaretçi yaklaşımınız çıktıyı sıkıştırmanın güzel bir yoludur.
Mark Ransom

0

Tüm öğeleri bir binary tree the disregards duplicates- içine ekleyin O(nlog(n)). Ardından, çaprazlama yaparak hepsini diziye geri alın - O(n). Sipariş korumasına ihtiyacınız olmadığını varsayıyorum.


0

Hashing için çiçek filtresini kullanın. Bu, bellek ek yükünü önemli ölçüde azaltacaktır.


detaylandırmak veya bir referans sağlamak ister misiniz?
dldnh

0

JAVA'da,

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

çıktı: {1, 2, 3, 4, 6, 7, 8, 9, 10}

umarım bu yardımcı olur


1
Bunu girişle test edinarrayInteger = {100,10,1};
Blastfurnace


0

İlk olarak, check[n]n'nin kopyasız yapmak istediğiniz dizinin öğe sayısı olduğu bir dizi oluşturmalı ve her öğenin (denetim dizisinin) değerini 1'e eşitlemelisiniz. Bir for döngüsü kullanarak diziyi kopyalar, adını söyleyin ve arrfor-döngüsüne şunu yazın:

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

Bununla, her kopyayı sıfıra eşitlersiniz. Yani geriye kalan tek şey arrdiziyi geçmek ve sıfıra eşit olmayan her şeyi yazdırmak. Sıra kalır ve doğrusal zaman alır (3 * n).


Soru, ekstra veri yapısının kullanılmasına izin vermiyor.
ejel

0

Bir dizi n eleman verildiğinde, O (nlogn) zamanında dizideki tüm kopyaları kaldırmak için bir algoritma yazın

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.

Diğer öğelerde, 'anahtar' kullanılarak çıktı dizisinde tutulur. Anahtarın O (n) uzunluğunda olduğunu, anahtar üzerinde sıralama yapmak için geçen sürenin ve değerin O (nlogn) olduğunu düşünün. Dolayısıyla diziden tüm kopyaları silmek için geçen süre O (nlogn) olur.


Tüm cesur glifler için ne düşündünüz helper data structure (e.g. hashtable) should not be used?
greybeard

Mutlaka gerekli değildir. Sadece anlamak amacıyla bunların altını çizdim.
Sharief Muzammil

0

Elimde olan bu, ancak düzeltmek için artan veya azalan sıralayabileceğimiz sırayı yanlış yerleştirse de.

#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

Bir tamsayı içerip içermediğini hızlı bir şekilde anlayabilen iyi bir DataStructure'unuz olsaydı harika olurdu. Belki bir çeşit ağaç.

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;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.