Bir öğeyi aramanın verimli yolu


88

Geçenlerde bir röportaj yaptım ve bana bir " arama " sorusu sordular .
Soru şuydu:

Her bir öğenin bitişiğindeki öğeleriyle karşılaştırıldığı +1veya -1bunlarla karşılaştırıldığı bir (pozitif) tamsayı dizisi olduğunu varsayalım .

Misal:

Şimdi 7konumunu arayın ve geri dönün.

Bu cevabı verdim:

Değerleri geçici bir dizide saklayın, sıralayın ve ardından ikili arama uygulayın.

Öğe bulunursa, geçici dizideki konumunu döndürün.
(Sayı iki kez oluyorsa, ilk geçtiği yeri döndür)

Ancak, bu cevapla tatmin olmadılar.

Doğru cevap nedir?


4
Bildiğim kadarıyla doğrusal arama, dizideki bir öğenin dizinini bulmanın iyi bir yoludur. Henüz bir elemanın indeksini bulmada etkili olan başka bir arama algoritmasından emin değilim.
Sean Francis N. Ballais

4
7'nin yalnızca bir kez görünmesi garanti edilirse veya hangi 7'nin döndürüldüğü önemli değilse, Coleman'ın cevabının doğrusal algoritmasını biraz daha geliştirebilirsiniz.
user1942027

52
Orijinal çözümünüz sıralama gerektiriyorsa, saf doğrusal aramadan daha kötüdür. Bunun farkında değilmişsin.
cubuspl42

5
Sıralama O (nlogn) gerektirir ve ikili arama O (logn) şeklindedir. Büyük diziden birçok değeri aramanız gerekiyorsa, cevabınız daha iyi olabilir, ancak yalnızca bir kez arama yaparsanız, O (n) algoritmaları daha iyi olabilir.
jingyu9575

23
Neden başka hiç kimsenin bundan bahsetmediğini bilmiyorum: yönteminiz sadece verimsiz değildi, yanlıştı ve bu sadece verimsizlikten çok daha kötü. Gereksinim, belirli bir sayının orijinal dizideki konumu içindir . Yönteminiz, sıralı bir dizideki sayının konumunu döndürür . Şimdi, basit diziyi sıralamadan önce bir demet dizisine (sayı, orig_pos) dönüştürerek orijinal konumu elde edebilirsiniz . Ama bundan bahsetmedin, bu yüzden röportajda da bahsetmedin sanırım.
Tom Zych

Yanıtlar:


126

Genellikle 1'den büyük adımlarla doğrusal bir arama yapabilirsiniz. Önemli gözlem, örneğin array[i] == 4ve 7 henüz ortaya çıkmadıysa, 7 için bir sonraki adayın indekste olmasıdır i+3. Bir sonraki geçerli adaya art arda doğrudan giden bir while döngüsü kullanın.

İşte biraz genelleştirilmiş bir uygulama. Dizideki ilk oluşumunu bulur k(+ = 1 kısıtlamasına tabidir) veya -1gerçekleşmezse:

çıktı:


8
Ben de tam olarak ne düşünüyordum. Bu O(N), ama bunu yapmanın daha hızlı bir yolu olduğunu sanmıyorum.
shapiro yaacov

2
Bunu ortalama olarak daha fazla adayla (örneğin ilk ve sonuncu) biraz daha hızlı yapabilir ve ardından hedefe en yakın olanla gidebilirsiniz - yani, ilkini değil, yalnızca tek bir oluşumu bulmanız gerekiyorsa.
mkadunc

2
@mkadunc Bu iyi bir fikir. Bir başka gözlem de, eğer ilk ve son elemanlar 7'yi ikiye ayırırsa, o zaman bu özel durumda ikili arama kullanabilirsiniz (hangi 7'yi bulduğunuza aldırış etmiyorsanız)
John Coleman

1
Herhangi bir 7 bulmanız gerektiğinde (ilki olması gerekmez), aşağıdaki (pratik) iyileştirmeyi öneriyorum. Bölümlerin bir listesini yapın (iki tam sayı, 'başlangıç' ve 'bitiş') ve dizinin başlangıcından başlamak yerine ortadan başlayın. Hücredeki değere göre ilgili aralığı yok sayın ve kalan iki bölümü bölüm listenize ekleyin. Şimdi listedeki bir sonraki öğe için tekrarlayın. Bu hala 'O (n)', ancak bir hücreyi her kontrol ettiğinizde aralığın iki katını görmezden geliyorsunuz.
shapiro yaacov

3
@ShapiroYaacov: Bir bölümün her iki tarafındaki değerlerin düşükten yükseğe aralığının k (7) içerip içermediğini kontrol etmekle birlikte, bu kendi başına bir cevabı hak ediyor.
greybeard

35

Yaklaşımınız çok karmaşık. Her dizi elemanını incelemeniz gerekmez. İlk değerdir 4yüzden, 7olduğu en azından 7-4 uzak elemanları ve o atlayabilirsiniz.

Program çıkışı:

Düzenleme: @Raphael Miedl ve @Martin Zabel'in yorumlarından sonra iyileştirildi.


2
Bir nitpick, if ((skip = 7 - array[i]) < 1) skip = 1;bence onu fazla karmaşıklaştırıyor ve karmaşa yaratıyor. Eğer array[i] == 200sen almak -193ve Neden sadece bütün 193. atlamak rağmen sadece 1 ile her atlama i += abs(7 - array[i])?
user1942027

1
skip7 ile arasındaki mutlak farkı ayarlamalısınız array[i].
Martin Zabel

@Raphael Miedl hayır, bir öğe olmayacak 200, geçecektin 7.
Weather Vane

3
Bitişik değerlerdir sadece biz bu garantiyi yok @WeatherVane +1/ -1birbirinden. Yani öyle olabilir array[0] == 200ve diğerleri çoğunlukla ona aittir -1.
user1942027

1
@WeatherVane bu, öğenin her zaman dizide bulunduğu varsayımına sahiptir, bu durumda olmayabilir. -1 bu durumda geçerli bir dönüştür; Bu, sahip olduğunuz kodu oldukça değiştirir
Eugene

20

Geleneksel doğrusal aramanın bir varyasyonu, gitmek için iyi bir yol olabilir. Bir unsur seçelim diyelim array[i] = 2. Şimdi, array[i + 1]1 veya 3 (tek) olacak,array[i + 2] olacak, (yalnızca pozitif tamsayılar) 2 veya 4 (çift sayı) olacaktır.

Böyle devam edince, bir model gözlemlenebilir - array[i + 2*n]çift ​​sayıları tutacaktır ve bu nedenle tüm bu indeksler göz ardı edilebilir.

Ayrıca bunu görebiliriz

bu nedenle, daha sonra indeks i + 5kontrol edilmelidir ve indekste bulunan değere bağlı olarak kontrol edilecek bir sonraki indeksi belirlemek için bir while döngüsü kullanılabilir i + 5.

Bu karmaşıklığa sahipken O(n)(asimptotik karmaşıklık açısından doğrusal zaman), tüm endeksler ziyaret edilmediğinden pratik açıdan normal bir doğrusal aramadan daha iyidir.

Açıkçası, eğer array[i](başlangıç ​​noktamız) tuhaf olsaydı tüm bunlar tersine çevrilecek .


8

John Coleman tarafından sunulan yaklaşım, görüşmecinin büyük olasılıkla umduğu şeydi.
Biraz daha karmaşık hale gelmek istiyorsanız, beklenen atlama uzunluğunu artırabilirsiniz:
Hedef değeri k olarak adlandırın . P konumundaki ilk elemanın değeri v ile başlayın ve mutlak değer av ile kv dv farkını çağırın . Negatif aramaları hızlandırmak için, o konumundaki diğer u değeri olarak son öğeye bir göz atın: dv × du negatifse, k mevcutsa (herhangi bir k oluşumu kabul edilebilirse, indeks aralığını burada ikili aramanın yaptığı gibi daraltabilirsiniz). Av + au dizinin uzunluğundan büyükse, k yoktur. (Eğer dv × du sıfırsa, v veya u k'ye eşittir.)
İndeks geçerliliğini atlamak: Sıranın ortada k ile v'ye dönebileceği ("sonraki") konumu araştırın o = p + 2*av.
Dv × du negatifse, p + av'dan o-au'ya k (yinelemeli olarak?) Bulun;
sıfır ise, u eşittir k at o.
Du dv eşittir ve varsa ortada değer k değildir veya au av aşıyor,
ya da başarısız + av için o-au, s dan k bulmak için
izinp=o; dv=du; av=au; ve sondalama tutun.
(60'lı yılların metinlerine tam bir geri dönüş için Courier ile görüntüleyin. Benim "2. düşüncem"o = p + 2*av - 1, du equals dv'yi engeller .)


4

AŞAMA 1

İlk elemanla başlayın ve 7 olup olmadığını kontrol edin. Diyelim ki cmevcut konumun indeksi. Yani, başlangıçta c = 0.

ADIM 2

7 ise, dizini buldunuz. Bu c. Dizinin sonuna ulaştıysanız, çıkın.

AŞAMA 3

Değilse, |array[c]-7|endeks başına yalnızca bir birim ekleyebileceğiniz için 7 en az konum uzakta olmalıdır . Bu nedenle, |array[c]-7|mevcut indeksinize ekleyin , c ve kontrol etmek için ADIM 2'ye tekrar gidin.

En kötü durumda, alternatif 1 ve -1'ler olduğunda, zaman karmaşıklığı O (n) değerine ulaşabilir, ancak ortalama durumlar hızlı bir şekilde teslim edilir.


Bunun John Coleman'ın cevabından farkı nedir? ( |c-7|Nereye |array[c]-7|çağrıldığını
belirtmenin dışında

Cevabını şimdi gördüm. Temel fikrin aynı olduğunu kabul ediyorum.
Akeshwar Jha

Orijinal soru, dizinin 7'den küçük bir sayıyla başladığını şart koşmaz. Bu nedenle array[c]-7pozitif veya negatif olabilir. abs()İleri atlamadan önce başvurmanız gerekir .
arielf

Evet haklısın. Bu yüzden array[c] - 7modül operatörü ile kullanıyorum |array[c] - 7|.
Akeshwar Jha

4

Burada java uygulamasını veriyorum ...


2
En azından yarı resmi bir konvansiyona sahip bir dilde belgelenmemiş kod . Bu, "c" etiketini özgürce yorumlamaktan başka, John Coleman ve Akeshwar'ın yanıtlarından nasıl farklı?
greybeard

3

İşte böl ve yönet tarzı bir çözüm. Daha fazla defter tutma pahasına, daha fazla öğeyi atlayabiliriz; soldan sağa taramak yerine ortada test edin ve her iki yönde de atlayın .


neal-fultz cevabınız ilk geçtiği yeri değil, ortadan başlayıp her iki taraftan atlarken arama öğesinin herhangi bir rastgele oluşumunu döndürür.
Ram Patra

Özyineleme sırasının değiştirilmesi okuyucuya bir alıştırma olarak bırakılmıştır.
Neal Fultz

1
neal-fultz sonra lütfen printf () yöntem çağrınızdaki mesajı düzenleyin.
Ram Patra

2

Soruna özyinelemeli bir çözüm eklemek istedi. Zevk almak

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.