Longest Increasing Subsequence
Bir tamsayı listesi nums alırsınız. Bir alt dizi, öğelerin bazılarını özgün sıralarında tutar ve geri kalanları çıkarır; tutulan öğelerin yan yana olması gerekmez. Değerleri soldan sağa kesin olarak artan en uzun alt dizinin uzunluğunu döndürün. Art arda gelen iki eşit değer artış olarak sayılmaz.
Fonksiyon
- numsinteger-array
- Seçim yapılacak tamsayıların listesi
- Döndürürinteger
- en uzun kesin artan alt dizinin uzunluğu
Kısıtlar
1 ≤ nums.length ≤ 2500-104 ≤ nums[i] ≤ 104
Örnekler
- Girdi
- nums = [3, 1, 8, 2, 5, 9, 4, 7]
- Çıktı
- 4
- Açıklama
- 1, 2, 5, 9 değerlerini tutmak, uzunluğu 4 olan artan bir alt dizi verir; 1, 2, 5, 7 ve 1, 2, 4, 7 de aynı şekilde. Beş değerlik hiçbir seçim sürekli artmaz, dolayısıyla cevap 4'tür.
- Girdi
- nums = [7, 7, 7, 7]
- Çıktı
- 1
- Açıklama
- Değerler kesin olarak artmalıdır; bu nedenle 7'lerden hiçbiri aynı alt dizide yer alamaz. Tek başına bir eleman da sayılır; bu yüzden cevap 1'dir.
- Girdi
- nums = [12, -4, 0, 25, -10, 3, 16, 5]
- Çıktı
- 4
- Açıklama
- -4, 0, 3, 16 dizisinin uzunluğu 4'tür (-4, 0, 3, 5 dizisinin uzunluğu da öyledir). İlk öğe olan 12'den başlamak yalnızca iki değer elde etmenizi sağlar; örneğin 12, 25: en iyi alt dizinin en baştan başlaması gerekmez.
Gönderirken +20 gizli test
Ek soru
Yalnızca uzunluğunu değil, en uzun artan alt dizinin kendisini de döndürebilir ve bunu yine O(n log n) zamanda yapabilir misin?
İpuçları
Tek tek açın. Her biri biraz daha fazlasını gösterir.
Tüm listenin en iyi alt dizisini doğrudan açıklamak zordur. Her
iindeksi için daha dar kapsamlı bir soru sor: Tam olaraknums[i]ile biten en uzun artan alt dizi nedir?nums[i]ile biten bir alt dizi ya yalnızcanums[i]değerinden oluşur ya da daha öncekinums[j] < nums[i]değerlerinden birinde biten en iyi alt diziyi sürdürür. Bu türjdeğerleri arasından en iyisini seçip bir ekleyin. Cevap, nerede bittiğine bakılmaksızın bu değerlerin en büyüğüdür.O(n²)'nin altına inmek için her uzunluk için, o uzunluktaki bir alt dizinin bitebileceği en küçük değeri tut. Bu değerler sıralı kalır; böylece ikili arama, yeni bir sayının en uzun alt diziyi uzatıp uzatmadığını ya da bir bitiş değerinin yerini alıp almadığını söyler.
Çözüm
Bir alt dizi herhangi bir elemanı atlayabilir; bu nedenle n sayıdan oluşan bir listenin 2^n alt dizisi vardır ve hepsini kontrol etmek çok fazla zaman alır. Dinamik programlama çözümü, her indeks için daha dar bir soru sormaktır: Tam olarak burada biten en uzun artan alt dizi ne kadar uzun? Bu, O(n²) boyutunda bir tablo verir. En hızlı sürüm, her uzunluk için tek bir sayı tutar: o uzunluktaki bir alt dizinin bitebileceği en küçük değer. Ardından her yeni elemanı ikili aramayla yerleştirir.
Her öğeyi al ya da atla
Doğru, ama en büyük testlerde bitmiyor
Sezgi
Listeyi baştan sona gözden geçir ve her öğe için bir karar ver: tut ya da dışarıda bırak. nums[i] öğesini yalnızca tuttuğun son değerden büyükse tutabilirsin. Özyinelemeli longest(i, prev) fonksiyonu şu soruyu yanıtlar: Tutulan son öğe prev indeksindeyse (ya da henüz hiçbir şey tutulmadığında -1 ise), i indeksinden itibaren kaç öğe daha ekleyebilirsin?
Atlamak longest(i+1, prev) sonucunu verir. İzin verildiğinde tutmak ise 1 + longest(i+1, i) sonucunu verir. Yanıt bu ikisinden büyük olanıdır; listenin sonuna gelindiğinde artık başka bir şey eklenemeyeceğinden sonuç 0 olur. Her artan alt dizi, tutma ve atlama seçimlerinden oluşan bir yoldur; bu nedenle arama en iyisini kaçırmaz.
Değerler arttığında her iki dal da açık kaldığı için bu yöntem yavaştır. 1, 2, 3, ..., n gibi bir listede çağrı sayısı her öğeyle ikiye katlanır: 2 üzeri 40, şimdiden yaklaşık 10^12 çağrı demektir ve büyük testlerde 2500 öğe bulunur. Ancak longest(i, prev) yalnızca (i, prev) çiftine bağlıdır; dolayısıyla en fazla n² farklı soru vardır. Her soruyu bir kez sormak, sıradaki yaklaşımdır.
Algoritma
longest(i, prev)işlevini yazın; buradaprev, tutulan son öğenin dizini ya da-1olur.idizinin sonunu geçtiyse 0 döndürün.nums[i]öğesini atlayın:best = longest(i+1, prev).prevdeğeri-1ise veyanums[i] > nums[prev]ise öğeyi tutun:best = max(best, 1 + longest(i+1, i)).bestdeğerini döndürün. Yanıtlongest(0, -1)olur.
def lengthOfLIS(nums):
def longest(i, prev):
# The longest increasing subsequence of nums[i:] whose values all exceed nums[prev].
# prev is -1 while nothing has been taken.
if i == len(nums):
return 0
best = longest(i + 1, prev) # skip nums[i]
if prev == -1 or nums[i] > nums[prev]:
best = max(best, 1 + longest(i + 1, i)) # take nums[i]
return best
return longest(0, -1)Her dizinde sona eren en uzun alt dizi
Sezgi
Durum. ending[i], son elemanı nums[i] olan en uzun artan alt dizinin uzunluğu olsun. Son elemanı sabitlemek, problemi düzgün bir şekilde parçalara ayırmayı sağlar: Bir alt dizinin nerede bittiğini bildiğinde, onu hangi sonraki değerlerin izleyebileceğini bilirsin.
Özyineleme bağıntısı. nums[i] noktasında biten alt dizide birden fazla eleman varsa, nums[i] değerinden önceki eleman, j < i ve nums[j] < nums[i] koşullarını sağlayan bir nums[j] değeridir ve bu elemana kadarki kısım mümkün olduğunca uzun olmalıdır. Dolayısıyla ending[i] = 1 + max(ending[j]); burada j bu koşulları sağlayan indekslerdir. Temel durum: Her eleman tek başına bir alt dizi olduğundan ending[i] başlangıçta 1 olur. Sıra: ending[i] yalnızca daha küçük indeksleri okur, bu yüzden soldan sağa doldur.
[3, 1, 8, 2, 5, 9, 4, 7] için tablo [1, 1, 2, 2, 3, 4, 3, 4] olur. Örneğin 5, 3'ün, 1'in veya 2'nin ardından gelebilir ve bunların en iyisi ending = 2 olan 2'dir; dolayısıyla ending[4] = 3 olur. Yanıt en büyük değer olan 4'tür, son değer değil: en iyi alt dizi herhangi bir yerde bitebilir.
Her indeks, kendisinden önceki her indekse bir kez bakar; dolayısıyla işlem sayısı n(n-1)/2 karşılaştırmadır. n = 2500 için bu yaklaşık 3.1 × 10^6 karşılaştırma eder.
Algoritma
- Her girdisi 1 olacak şekilde
endingoluşturun. - Soldan sağa her
iiçin tümj < ideğerlerine bakın. nums[j] < nums[i]ise, daha büyük olduğundaending[i]değeriniending[j] + 1olarak ayarlayın.endingiçindeki en büyük değeri döndürün.
def lengthOfLIS(nums):
n = len(nums)
# ending[i]: the longest increasing subsequence that ends with nums[i]
ending = [1] * n
for i in range(1, n):
for j in range(i):
if nums[j] < nums[i] and ending[j] + 1 > ending[i]:
ending[i] = ending[j] + 1
return max(ending)İkili aramayla en küçük kuyruklar
Sezgi
Yukarıdaki tablo, her indeks için bir uzunluk saklar. Daha azını saklayabilirsin: her uzunluk için, o uzunluktaki artan bir alt dizinin bitebileceği en küçük değeri sakla. Uzunluk k+1 için buna tails[k] adını ver. Daha küçük bir bitiş değeri her zaman en az onun kadar iyidir; çünkü 9 ile biten bir alt diziyi izleyebilen herhangi bir değer, 5 ile biten bir alt diziyi de izleyebilir.
tails her zaman kesin artan sıradadır: t ile biten, k+2 uzunluğundaki bir alt dizi, t'den küçük bir değerle biten k+1 uzunluğunda bir alt dizi içerir. Bu yüzden her yeni x değeri için, ≥ x olan ilk kuyruğu ikili aramayla bul. Böyle bir kuyruk yoksa x tüm kuyruklardan büyüktür ve en uzun alt diziyi uzatır; bu nedenle sona ekle. Aksi takdirde o kuyruğu x ile değiştir: bir eleman daha kısa olan alt dizi x'ten küçük bir değerle biter; dolayısıyla x'i eklemek, daha küçük bir bitiş değeriyle aynı uzunluğu verir.
[3, 1, 8, 2, 5, 9, 4, 7] için tails sırasıyla [3], [1], [1, 8], [1, 2], [1, 2, 5], [1, 2, 5, 9], [1, 2, 4, 9], [1, 2, 4, 7] olur ve uzunluğu 4 olan cevap budur. [1, 2, 4, 9] adımında 4, girdide 9'dan sonra geldiği için tails kendi başına bir alt dizi değildir; yalnızca uzunluğunun bir anlamı vardır. Bu yönteme, her kuyruğun bir yığının en üst kartı olduğu kart oyunundan esinlenerek sabır sıralaması da denir.
Her eleman, en fazla n kuyruk üzerinde bir ikili arama gerektirir: en büyük girdi için yaklaşık 2500 × 12 = 30,000 adım.
Algoritma
- Boş bir
tailslistesiyle başlayın. - Her
xiçinnumsiçinde,tails[k] ≥ xkoşulunu sağlayan ilkkindeksini ikili aramayla bulun. - Hiçbir kuyruk
≥ xdeğilsexdeğerini sona ekleyin. - Aksi takdirde
tails[k] = xolarak ayarlayın. tailsuzunluğunu döndürün.
from bisect import bisect_left
def lengthOfLIS(nums):
# tails[k]: the smallest last value of any increasing subsequence of length k + 1
tails = []
for x in nums:
k = bisect_left(tails, x) # the first tail that is >= x
if k == len(tails):
tails.append(x) # x extends the longest subsequence so far
else:
tails[k] = x # x is a smaller ending for length k + 1
return len(tails)
Tuzaklar ve uç durumlar
Yanlış yanıtların çoğu, tablonun ne tuttuğunu karıştırmaktan veya eşit değerleri artan değerler olarak değerlendirmekten kaynaklanır.
- En büyük girdi yerine
ending[n-1]değerini döndürmek.[1, 2, 3, 0]için son girdi 1'dir, ancak yanıt 3'tür. <yerine≤ile karşılaştırmak.[7, 7, 7, 7]4 değil, 1 döndürmelidir.tailssürümünde, ilk kuyruğu≥ xyerine> xolacak şekilde aramak. Yinelenen değerlerde bu, ikinci 7'yi birinciden sonra ekler ve eşit değerleri daha uzun bir alt dizi olarak sayar.tailsdeğerlerini alt dizinin kendisi olarak değerlendirmek. Bu değerler farklı alt dizilerden gelebilir; bu nedenleparentsdeğerlerini ayrıca takip etmiyorsanız bunları yazdırmayın.- Yanlışlıkla bitişik sürümü çözmek.
[3, 1, 8, 2, 5, 9, 4, 7]içinde komşu elemanlardan oluşan en uzun artan dizi 2, 5, 9'dur (uzunluğu 3); yanıt ise 4'tür. - Lua ve R'de diziler 1'den başladığı için, 0 tabanlı
prev = -1işaretçisi 0 olur ve ikili arama, 1'den geçerli boyuta kadar olan indeksler üzerinde çalışır.
Sıkça sorulan sorular4
En Uzun Artan Alt Dizi'nin zaman karmaşıklığı nedir?
tails yöntemi O(n log n) zamanda ve O(n) alanda çalışır: her eleman için bir ikili arama. Her indeks çifti üzerindeki dinamik programlama tablosu O(n²) zaman alır ve her alt diziyi denemek O(2ⁿ) zaman alır. n = 2500 için bunlar yaklaşık 30.000, 3 milyon ve astronomik sayıda adımdır.
Sabır sıralama yöntemi neden doğru uzunluğu verir?
Her elemandan sonra, tails[k], şimdiye kadar görülen uzunluğu k+1 olan herhangi bir artan alt dizinin bitebileceği en küçük değeri tutar. x yalnızca tüm kuyruk değerlerinden büyük olduğunda eklenir; bu da artık önceki tüm alt dizilerden bir eleman daha uzun bir alt dizinin var olduğu anlamına gelir. Değiştirme uzunluğu hiçbir zaman değiştirmez, yalnızca bitiş değerini düşürür; bu nedenle listenin uzunluğu her zaman en uzun artan alt dizinin uzunluğudur.
Yalnızca uzunluğunu değil, gerçek en uzun artan alt diziyi nasıl elde edersiniz?
Her öğe için bir ebeveyn kaydedin. O(n²) tablosunda, i öğesinin ebeveyni, ending[i] değerini sağlayan j öğesidir. tails yönteminde, her kuyruğun arkasındaki öğenin dizinini saklayın ve bir öğe yerleştirildiğinde ebeveynini, solundaki konumda saklanan dizin olarak ayarlayın. Ardından, en uzun alt dizinin sonundan başlayarak ebeveynleri geriye doğru izleyin ve sonucu ters çevirin.
Onun yerine en uzun azalmayan alt diziyi nasıl bulursunuz?
Eşit komşulara izin verin. Tabloda nums[j] ≤ nums[i] kullanın. tails yönteminde, büyük veya eşit olanı bulmak yerine x değerinden kesin olarak büyük olan ilk kuyruğu arayın; böylece eşit bir değer kuyruğun yerini almak yerine listeyi uzatır. [7, 7, 7, 7] bu durumda 4 döndürür.
Benzer problemler
Aynı fikirleri kullanan problemler. İki üçünü çözmek bir kalıbı kalıcı hale getirir.
Python
def lengthOfLIS(nums):
# Kodu buraya yazınDurum 1
Durum 2
Durum 3
Girdi
nums = [3, 1, 8, 2, 5, 9, 4, 7]
Beklenen
4