Longest Common Subsequence
İki dizge alırsın: text1 ve text2. Bir dizgenin alt dizisi, harflerinin bazılarını özgün sıralarında tutup geri kalanları çıkarır; tutulan harflerin yan yana olması gerekmez. Her iki dizgenin de alt dizisi olan en uzun dizgenin uzunluğunu döndür; iki dizgede ortak harf yoksa 0 döndür.
Fonksiyon
- text1string
- ilk dize
- text2string
- ikinci dize
- Döndürürinteger
- en uzun ortak alt dizinin uzunluğu
Kısıtlar
1 ≤ text1.length ≤ 10001 ≤ text2.length ≤ 1000- Her iki dize de yalnızca küçük İngilizce harfler içerir.
Örnekler
- Girdi
- text1 = "stone"text2 = "longest"
- Çıktı
- 3
- Açıklama
- o, n, e her iki sözcükte de bu sırayla görünür, bu nedenle
oneuzunluğu 3 olan ortak bir alt dizidir.longestsözcüğünde s ve t harfleri sonda gelirken,stonesözcüğünde başta gelir; bu yüzden bunları kullanan ortak bir alt dizi yalnızcastolabilir ve bu daha kısadır.
- Girdi
- text1 = "pear"text2 = "reap"
- Çıktı
- 2
- Açıklama
- Her iki kelimede de
eabulunur. İki kelimede de p ve r,eaifadesinin zıt taraflarında yer alır; bu nedenle hiçbiri ona katılamaz ve cevap 2'dir.
- Girdi
- text1 = "cat"text2 = "dog"
- Çıktı
- 0
- Açıklama
- İki sözcük hiçbir harfi paylaşmadığından, tek ortak alt dizi boş olandır ve uzunluğu 0'dır.
Gönderirken +19 gizli test
Ek soru
En uzun ortak alt dizinin uzunluğunu değil, kendisini döndürebilir misin?
İpuçları
Tek tek açın. Her biri biraz daha fazlasını gösterir.
Her dizenin son harfine bakın. İki harf aynı olduğunda yanıt hakkında ne söyleyebilirsiniz, farklı olduklarında ne söyleyebilirsiniz?
Harfler eşleşiyorsa onları eşleştir; geriye kalan, her iki dizgede de o harf çıkarıldıktan sonra aynı problemdir. Farklılarsa ikisinden en az biri kullanılmıyordur; bu yüzden her birini çıkarmayı deneyip daha iyi sonucu seç.
Aynı önek çiftleri tekrar tekrar karşımıza çıkar. Her önek uzunluğu çifti
(i, j)için yanıtı bir tabloda saklayın, yanıtı 0 olan boş öneklerden başlayın, tabloyu satır satır doldurun ve yanıtı son hücreden okuyun.
Çözüm
Harfleri açgözlü bir şekilde eşleştirmek işe yaramaz. Bir harf, diğer dizgede birçok yerde eşleşebilir ve ilk eşleşme daha iyi eşleşmeleri engelleyebilir: cab içindeki c harfini abc dizgesinin sonundaki c ile eşleştirmek, a ve b için eşleşme bırakmaz; oysa bu harfi atlamak ab sonucunu bulur. Bu sorunu çözen fikir, iki önek için cevabın yalnızca biraz daha kısa öneklerin cevaplarına bağlı olmasıdır. (n+1) × (m+1) sayıdan oluşan bir tablo, her çifti bir kez çözer ve her satır yalnızca üstündeki satırı okuduğundan iki satır yeterlidir.
İlk harfleri özyinelemeyle karşılaştır
Doğru, ama en büyük testlerde bitmiyor
Sezgi
lcs(i, j), text1[i:] ve text2[j:] son ekleri için yanıt olsun. İlk harflerine bakın. Eşitlerse onları eşleştirin: bu eşleşmeyi kullanmayan en uzun ortak alt dizi, ilk eşleşmesini bununla değiştirerek kısalmadan devam edebilir. Dolayısıyla yanıt 1 + lcs(i+1, j+1) olur.
Harfler farklıysa ikisi birden kullanılamaz; çünkü her biri yalnızca diğer dizenin daha sonraki bir harfiyle eşleşebilir ve eşleşmeler kesişir. Bu nedenle biri çıkarılabilir: yanıt max(lcs(i+1, j), lcs(i, j+1)) olur. Son eklerden biri boş olduğunda ortak hiçbir şey yoktur ve yanıt 0'dır.
Yavaştır, çünkü her uyuşmazlık iki çağrı başlatır. Dizeler hiçbir harfi paylaşmıyorsa, bir dize tükenene kadar her çağrıda uyuşmazlık olur ve çağrı sayısı iki dizenin iç içe geçirilme yollarının sayısı gibi büyür. 20 harfli iki dize için bu yaklaşık 2.8 × 10^11 çağrıdır; büyük testlerde her biri 1000 harf uzunluğundadır. Oysa yalnızca (n+1) × (m+1) farklı (i, j) çifti vardır, dolayısıyla neredeyse her çağrı daha önce yapılmış birini tekrarlar.
Algoritma
ivejkonumlarından başlayan son ekler içinlcs(i, j)yazın.iveyajkendi dizgesinin sonunu geçmişse 0 döndürün.text1[i] == text2[j]ise1 + lcs(i+1, j+1)döndürün.- Aksi hâlde
max(lcs(i+1, j), lcs(i, j+1))döndürün. - Yanıt
lcs(0, 0)değeridir.
def longestCommonSubsequence(text1, text2):
def lcs(i, j):
# The longest common subsequence of text1[i:] and text2[j:]
if i == len(text1) or j == len(text2):
return 0
if text1[i] == text2[j]:
return 1 + lcs(i + 1, j + 1)
return max(lcs(i + 1, j), lcs(i, j + 1))
return lcs(0, 0)Önekler tablosunu doldurun
Sezgi
Durum. dp[i][j], text1 dizgesinin ilk i harfi ile text2 dizgesinin ilk j harfinin en uzun ortak alt dizisi olsun. Öneklerle çalışmak, 0 indeksinin boş dizgeyi ifade etmesini sağlar.
Bağıntı. İki önekin son harflerini, text1[i-1] ve text2[j-1] değerlerini karşılaştır. Eşitlerse onları eşleştir: dp[i][j] = dp[i-1][j-1] + 1. Eşit değillerse birini çıkar: dp[i][j] = max(dp[i-1][j], dp[i][j-1]). Bu, sondan okunarak yapılan özyinelemeyle aynı mantıktır. Temel durum: 0. satır ve 0. sütun 0'dır; çünkü boş bir önekin hiçbir şeyle ortak harfi yoktur. Sıra: Her hücre üstündeki, solundaki ve çapraz olarak sol üstündeki hücreyi okur; bu nedenle satır satır, soldan sağa doldurmak, bu hücrelerin her zaman hazır olmasını sağlar. Yanıt dp[n][m] değeridir.
pear ve reap için pea satırı [0, 0, 1, 2, 2] olur. rea için olan hücresi 2'dir; çünkü a, a ile eşleşir ve bu nedenle pe ile re için olan 1 değerli hücreye bir eklenir. Son hücre, pear ile reap karşılaştırması, birbirinden farklı olan r ve p harflerini karşılaştırır ve iki komşusundan büyük olanı, yani 2'yi alır.
Tabloda (n+1) × (m+1) hücre bulunur ve her biri sabit miktarda işlem gerektirir: 1000 harfli iki dizge için yaklaşık 10^6 adım. Özyinelemenin belleğe alınmış sürümü aynı hücreleri doldurur, ancak n + m çağrı derinliğine kadar özyinelemeli çalışır; bu da Python gibi dillerde varsayılan çağrı yığını boyutunu aşar.
Algoritma
(n+1) × (m+1)sıfırdan oluşan birdptablosu oluşturun.iiçin 1'denn'ye vejiçin 1'denm'ye kadar,text1[i-1]iletext2[j-1]'yi karşılaştırın.- Eşleşme varsa
dp[i][j] = dp[i-1][j-1] + 1olarak ayarlayın. - Aksi takdirde
dp[i][j] = max(dp[i-1][j], dp[i][j-1])olarak ayarlayın. dp[n][m]değerini döndürün.
def longestCommonSubsequence(text1, text2):
n, m = len(text1), len(text2)
# dp[i][j]: the longest common subsequence of text1[:i] and text2[:j].
# Row 0 and column 0 stay 0: an empty prefix has nothing in common.
dp = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, m + 1):
if text1[i - 1] == text2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[n][m]Yalnızca iki satır bırakın
Sezgi
Tablonun i. satırı yalnızca i-1. satırı ve kendi önceki hücrelerini okur. Bir satır tamamlandıktan sonra üstündeki satırlar bir daha okunmaz. Bu yüzden tamamlanmış satır için prev, doldurulmakta olan satır için cur olmak üzere iki dizi tutun ve her satırdan sonra bunları yer değiştirin. Bağıntı ve sıralama tamamen aynı kalır.
İki dizenin ortak alt dizisi, hangi dizenin önce olduğuyla ilgilenmez; bu yüzden dizelerin yerini değiştirip satırların daha kısa olan dizi boyunca ilerlemesini sağlayabilirsiniz. Böylece her satırda min(n, m) + 1 sayı bulunur: en büyük girdiler için bir milyon hücre yerine 1001 hücre; yapılan iş yine 10^6 adımdır.
Her satırın ilk girdisi, kısa dizenin boş bir önekini temsil eder; bu yüzden 0 olarak kalmalıdır. Yanıt, tamamlanmış son satırın son girdisidir.
Algoritma
text2,text1'den daha uzunsa, onları yer değiştir.- Her biri
m + 1sıfır içerenprevvecuroluştur; buradamdaha kısa olanın uzunluğudur. text1'in her harfi için, yukarıdaki satır içinprev'i okuyarakcur[1..m]'i tablodaki kuralla doldur.prevvecur'un yerini değiştir.prev[m]'i döndür.
def longestCommonSubsequence(text1, text2):
if len(text2) > len(text1):
text1, text2 = text2, text1 # keep the rows as short as the shorter string
m = len(text2)
# prev[j]: the answer for the previous prefix of text1 and text2[:j]
prev = [0] * (m + 1)
for ch in text1:
cur = [0] * (m + 1)
for j in range(1, m + 1):
if ch == text2[j - 1]:
cur[j] = prev[j - 1] + 1
else:
cur[j] = max(prev[j], cur[j - 1])
prev = cur
return prev[m]
Tuzaklar ve uç durumlar
Özyineleme bağıntısı kısadır ve hataların çoğu bir fazla ya da bir eksik saymaktan veya eşleşmeyi yanlış yere eklemekten kaynaklanır.
- Tablo indekslerini dizge indeksleriyle karıştırmak.
dp[i][j]hücresitext1[i-1]iletext2[j-1]değerlerini karşılaştırır; çünkü 0. satır boş öneke karşılık gelir. - Eşleşme olduğunda,
dp[i-1][j-1]değerine eklemek yerinemax(dp[i-1][j], dp[i][j-1])değerine 1 eklemek. Bu, aynı harfin iki kez kullanılmasına yol açabilir:aaileakarşılaştırıldığında sonuç 1 yerine 2 olur. - İki işaretçiyle açgözlü eşleştirme yapmak.
cabileabckarşılaştırıldığında iki c harfi eşleştirilir ve sonuç 1 olurken,ab2 verir. - Hâlâ okuduğun satıra yazmak. İki satır kullanırken, yukarıdaki satırdaki her değer
prevüzerinden alınmalı vecur[0]0 kalmalıdır. - Yanlışlıkla en uzun ortak alt dizgeyi çözmek. Bir alt dizi harfleri atlayabilir; alt dizge atlayamaz.
- 1000 harfli dizgelerde özyineleme kullanarak önbelleğe alma yapmak. Çağrı derinliği 2000'e ulaşır; bu, Python'ın varsayılan 1000 sınırını aşar.
Sıkça sorulan sorular4
En Uzun Ortak Alt Dizi'nin zaman karmaşıklığı nedir?
Tablo çözümü O(n × m) zamanda çalışır; burada n ve m iki dizenin uzunluklarıdır: her önek çifti için bir hücre doldurur. Tablonun tamamı için O(n × m) bellek, iki satır kullanıldığında ise O(min(n, m)) bellek gerekir. Tablo kullanmayan yalın özyineleme üstel zamandadır.
En uzun ortak alt dizi ile en uzun ortak alt dize arasındaki fark nedir?
Bir alt dizi, sıra korunduğu sürece harfleri atlayabilir; alt dize ise yan yana gelen harflerden oluşan bir bloktur. stone ve longest için en uzun ortak alt dizi one (3), en uzun ortak alt dize ise on (2) olur. Alt dize sürümünde benzer bir tablo kullanılır, ancak eşleşme olmadığında hücre komşusundaki değeri kopyalamak yerine 0'a sıfırlanır.
En uzun ortak alt dizinin kendisini nasıl yazdırırsınız?
Tablonun tamamını doldurun, ardından dp[n][m] konumundan geriye doğru ilerleyin. Geçerli hücredeki iki harf eşleşiyorsa, bu harf yanıtın bir parçasıdır: onu kaydedin ve çapraz olarak yukarı ve sola ilerleyin. Aksi takdirde, daha büyük değeri taşıyan üstteki veya soldaki komşu hücreye ilerleyin. Sonunda kaydedilen harfleri ters çevirin. İki satırlı sürüm bunu yapamaz, çünkü önceki satırları silip atmıştır.
LCS, diff araçları ve düzenleme mesafesiyle nasıl ilişkilidir?
Bir dosyanın iki sürümü arasındaki fark, satırlarının en uzun ortak alt dizisini bulur; bu dizinin dışındaki her satır eklenmiş veya silinmiş olarak gösterilir. Aynı şekilde, bir dizgeyi diğerine dönüştürmek için gereken en az ekleme ve silme sayısı n + m - 2 × LCS olur. Düzenleme uzaklığı, bir harfi değiştirmeye de izin verir; bu nedenle her hücre için üçüncü bir seçenek içeren kendi tablosunu kullanır.
Benzer problemler
Aynı fikirleri kullanan problemler. İki üçünü çözmek bir kalıbı kalıcı hale getirir.
Python
def longestCommonSubsequence(text1, text2):
# Kodu buraya yazınDurum 1
Durum 2
Durum 3
Girdi
text1 = "stone" text2 = "longest"
Beklenen
3