Lowest Common Ancestor of a BST
Seviye sırasına göre tree dizisinde saklanan bir ikili arama ağacı ve bu ağaçta bulunan p ve q değerleri veriliyor. Kök 0 dizininde bulunur, i dizinindeki düğümün çocukları 2*i+1 (sol) ve 2*i+2 (sağ) dizinlerinde bulunur, boş bir yeri -1 belirtir ve dizinin sonunda fazladan -1 girdileri olabilir. İkili arama ağacında, bir düğümün sol alt ağacındaki her değer düğümün değerinden küçük, sağ alt ağacındaki her değer ise daha büyüktür.
lowestCommonAncestor adlı bir fonksiyon yazın. Bu fonksiyon, p ve q değerlerinin en düşük ortak atasının değerini döndürmelidir: her ikisini de alt ağacında bulunduran en derindeki düğüm. Bir düğüm kendi alt ağacının parçası sayılır; bu nedenle p, q'nun üstündeyse yanıt doğrudan p olur.
Fonksiyon
- treeinteger-array
- ikili arama ağacını seviye sırasıyla, boş konumlar için -1 kullanarak
- pinteger
- bulunacak ilk değer
- qinteger
- bulunacak ikinci değer
- Döndürürinteger
- Alt ağacında hem p hem de q bulunan en derin düğümün değeri
Kısıtlar
1 ≤ tree.length ≤ 32767- Her
tree[i],-1ya da0 ≤ tree[i] ≤ 105koşulunu sağlayan bir değerdir. tree[0]hiçbir zaman-1olmaz, bu nedenle ağaçta en az bir düğüm vardır.- Dizi, son düğümden sonra fazladan
-1girdileriyle bitebilir. - Boş bir noktanın her iki çocuğu da boştur ve derinlik en fazla
14olur. - Ağaç geçerli bir ikili arama ağacıdır, bu nedenle tüm değerleri birbirinden farklıdır.
pveqağaçtaki düğümlerin değerleridir. Herhangi bir sırada gelebilirler ve eşit olabilirler.
Örnekler
- Girdi
- tree = [20, 8, 31, 3, 12, 25, 40, -1, -1, 10, 15]p = 3q = 15
- Çıktı
- 8
- Açıklama
3,8'in sol çocuğudur ve15,8'in sağında12'nin altında yer alır. Her birinden yukarı doğru çıkıldığında, ikisinin de ulaştığı ilk düğüm8'dir; dolayısıyla cevap budur. Kök20de ortak bir atadır, ancak daha üsttedir.
- Girdi
- tree = [20, 8, 31, 3, 12, 25, 40, -1, -1, 10, 15]p = 12q = 10
- Çıktı
- 12
- Açıklama
10,12'nin sol çocuğudur. Bir düğüm kendisinin de atası sayılır; bu nedenle12'nin alt ağacında her iki değer de bulunur ve altındaki hiçbir düğümde ikisi birden bulunmaz: yanıt12'dir. Değerler her iki sırada da gelebilir; buradapdaha büyük olanıdır.
- Girdi
- tree = [50, 30, 70, 20, 40, 60, 80, -1, -1, -1, -1, 55]p = 55q = 80
- Çıktı
- 70
- Açıklama
- Hem
55hem de80, kök50'den büyüktür; bu yüzden ikisi de onun sağında yer alır.70'te yolları ayrılır:55daha küçüktür ve solda (60'ın altında) yer alır,80ise daha büyüktür ve sağda yer alır. Dolayısıyla cevap70'tir.
Gönderirken +12 gizli test
Ek soru
Tree'de p veya q eksik olabilseydi ve bu durumda fonksiyonun -1 döndürmesi gerekseydi neyi değiştirirdin?
İpuçları
Tek tek açın. Her biri biraz daha fazlasını gösterir.
Kök düğümde dur. Hem
phem deqonun değerinden küçükse, her iki düğüm de hangi alt ağaçtadır?Her iki değer de geçerli düğümün aynı tarafında olduğu sürece, daha derindeki tüm ortak atalar da o tarafta olur. Aynı tarafta olmadıkları ilk düğüm veya bu değerlerden birini içeren düğüm, aradığınız düğümdür.
0indeksinden başla. Her iki değer detree[i]değerinden küçükken2*i+1konumuna ilerle; her iki değer de daha büyükken2*i+2konumuna ilerle. Aksi hâldetree[i]değerini döndür.
Çözüm
Sıradan bir ikili ağaçta, her düğümün iki tarafını da aramadan bir değerin nerede bulunduğunu bilemezsiniz. Bir arama ağacı her düğümde şunu söyler: daha küçük değerler solda, daha büyük değerler sağdadır. Bu nedenle kökten başlayın ve her iki değeri de barındıran tarafa doğru ilerleyin. Değerlerin artık aynı tarafta olmadığı ilk düğüm yanıttır; bu düğümü tek bir yolu izleyerek bulursunuz ve ağacın geri kalanına hiç bakmazsınız.
Tüm ağacı, sıralamayı dikkate almadan ara
Sezgi
Öncelikle dizide nasıl ilerleyeceğimize bakalım. i indeksindeki düğümün sol çocuğu 2*i+1, sağ çocuğu ise 2*i+2 indeksindedir. Bir çocuk, ancak indeksi dizinin içindeyse ve o konumdaki değer -1 değilse gerçektir. [20, 8, 31, 3, 12, 25, 40, -1, -1, 10, 15] dizisinde, kök 20 indeksleri 1 ve 2 olan 8 ve 31 düğümlerine sahiptir; 4 indeksindeki 12 ise indeksleri 9 ve 10 olan 10 ve 15 düğümlerine sahiptir.
Bu ilk yöntem her ikili ağaçta çalışır. Özyinelemeli find(i), i konumundaki alt ağacın ne içerdiğini bildirir. Boş bir konum -1 bildirir. p veya q değerini taşıyan bir düğüm kendisini bildirir: diğer değer onun altındaysa yanıt bu düğümdür; diğer değer başka bir yerdeyse daha yukarıdaki bir düğüm her ikisini de görecektir. Aksi durumda düğüm her iki çocuğa da sorar. Her iki taraf da bir şey bildirirse p bir tarafta, q diğer taraftadır; dolayısıyla buluştukları düğüm bu düğümdür. Yalnızca bir taraf bir şey bildirirse, bildirilen değeri yukarı aktar.
p = 3 ve q = 15 için 8 düğümü, solundan indeks 3'ü, sağından ise indeks 10'u alır ve kendisini bildirir. Kök bu değeri solundan, sağından ise -1 alır ve 8 değerini yukarı aktarır.
Yöntem doğrudur, ancak her düğümü ziyaret edebilir: O(n) zaman ve özyineleme için O(h) alan kullanır. Değerlerin sıralamasından hiç yararlanmaz; oysa arama ağacının temel amacı budur.
Algoritma
find(i)yaz.ikonumu boşsa (sonun ötesindeyse veya-1ise),-1döndür.tree[i],pveyaqiseidöndür.findişlevini2*i+1ve2*i+2üzerinde çağır. Her ikisi de bir şey bulduysaidöndür.- Aksi takdirde, bir şey bulan tarafı döndür veya
-1döndür. tree[find(0)]döndür.
def lowestCommonAncestor(tree, p, q):
n = len(tree)
def find(i):
# In the subtree at index i: the index of the answer if both values are
# inside, the index of the one that is, or -1 when neither is.
if i >= n or tree[i] == -1:
return -1
if tree[i] == p or tree[i] == q:
return i
left = find(2 * i + 1)
right = find(2 * i + 2)
if left != -1 and right != -1:
return i # one value on each side: this node is the answer
return left if left != -1 else right
return tree[find(0)]İki arama yolunu karşılaştırın
Sezgi
Şimdi sıralamayı kullan. Bir değeri, arama ağacının aranması gerektiği şekilde bulabilirsin: kökten başla, değer düğümden küçükse sola, büyükse sağa git ve değere ulaştığında dur. Bu yürüyüş, değerin tüm atalarından ve başka hiçbir şeyden geçmez; çünkü kökten bir düğüme giden yol tektir.
p için yürüyüşü ve q için yürüyüşü kaydet. İkisi de kökten başlar ve değerler farklı yönlere gidene kadar aynı düğümleri izler. Ortak başlangıç, ortak atalarının listesidir; dolayısıyla ortak son değer en alttakidir. 3 ve 15 için yollar 20, 8, 3 ve 20, 8, 12, 15 şeklindedir: 20, 8 değerlerini paylaşırlar ve cevap 8'dir. 12 ve 10 için yollar 20, 8, 12 ve 20, 8, 12, 10 şeklindedir ve cevap 12'dir.
Her yürüyüş seviye başına bir adım sürer; dolayısıyla zaman O(h) olur ve burada ağaç kaç düğüm içerirse içersin en fazla 14 adımdır. İki liste O(h) alan kullanır.
Algoritma
path(target)yazın:0indeksinden başlayın,tree[i]değerini kaydedin, eşit olduğundatargetdeğerine ulaşınca durun; aksi takdirdetargetdaha küçükse2*i+1indeksine, daha büyükse2*i+2indeksine geçin.piçin yolu veqiçin yolu oluşturun.- Değerleri eşleştiği sürece her iki listede de baştan ilerleyin ve son eşleşmeyi aklınızda tutun.
- Paylaşılan son değeri döndürün.
def lowestCommonAncestor(tree, p, q):
def path(target):
# The values met on the way from the root down to target.
values = []
i = 0
while True:
values.append(tree[i])
if tree[i] == target:
return values
i = 2 * i + 1 if target < tree[i] else 2 * i + 2
to_p, to_q = path(p), path(q)
# Both paths start at the root; the answer is the last value they share.
answer = to_p[0]
for a, b in zip(to_p, to_q):
if a != b:
break
answer = a
return answerDeğerler ayrılana kadar aşağı in
Sezgi
p ve q aynı yönde ilerlediği sürece iki yol örtüşür, bu yüzden onları saklamanız gerekmez. İkisini aynı anda ilerletin. v değerini tutan bir düğümde, her iki değer de v değerinden küçükse ikisi de sol alt ağaçta bulunur ve v altındaki her ortak ata da sol alt ağaçtadır: sola gidin. İkisi de büyükse sağa gidin.
Aksi hâlde hedefe ulaşmışsınızdır. Ya değerlerden biri v değerinden küçük, diğeri büyük olduğundan farklı alt ağaçlarda yer alırlar ve v düğümünün hiçbir çocuğu ikisini birden içermez; ya da değerlerden biri v değerine eşittir ve bir düğüm kendi atasidir. Her iki durumda da v, ikisinin de üzerindeki en derin düğümdür.
Üçüncü örnekte kök 50, hem 55 hem de 80 değerlerinden küçük olduğundan sağa, 70 değerine gidersiniz. Burada 55 daha küçük ve 80 daha büyüktür: yanıt 70 olur. İkinci örnekte 20 değerinden 8 değerine, ardından 12 değerine gidersiniz; bu değer p'ye eşittir ve durursunuz.
Kökten başlayarak tek bir yolu izler, her seviyede bir karşılaştırma çifti yaparsınız; dolayısıyla zaman O(h), alan ise O(1) olur. Ağacın geri kalanı hiçbir zaman okunmaz.
Algoritma
i = 0indeksinden başla.v = tree[i]değerini oku.p < vveq < vise2*i+1konumuna git ve tekrarla.p > vveq > vise2*i+2konumuna git ve tekrarla.- Aksi takdirde
vdeğerini döndür.
def lowestCommonAncestor(tree, p, q):
i = 0 # start at the root
while True:
value = tree[i]
if p < value and q < value:
i = 2 * i + 1 # both are smaller: the answer is on the left
elif p > value and q > value:
i = 2 * i + 2 # both are larger: the answer is on the right
else:
return value # they split here, or one of them is this node
Tuzaklar ve uç durumlar
Yürüyüş kısadır, bu yüzden hataların çoğu durdurma kuralından kaynaklanır.
- Hareket testlerinde
≤ve≥kullanmak.p = 12veq = 10olduğunda,p ≤ 12veq ≤ 12testi yanıtı geçerek10değerine ilerler ve yürüyüş buradan10değerini döndürür ya da ağacın dışına çıkar. Yalnızca her iki değer de kesin olarak aynı taraftayken hareket edin. p < qolduğunu varsaymak. Değerler herhangi bir sırada gelebilir. İkisini de düğüme göre test edin ya da öncepdaha küçük olacak şekilde yerlerini değiştirin.- Bir değerin diğerinin atası olabileceğini unutmak. Bu durumda yanıt, o değerin kendisidir; ebeveyni değil.
- Değer yerine indeksi döndürmek. Fonksiyon
ideğil,tree[i]döndürür. - Tüm ağacı aramak. Bu doğru yanıtı verir, ancak tek bir yol yeterliyken ağaçtaki tüm düğümlere kadar ziyaret eder.
- Dizilerin 1'den başladığı Lua ve R dillerindeki ofseti karıştırmak.
2*i+1aritmetiği için düğüm indekslerini 0 tabanlı tutun vetree[i + 1]okuyun.
Sıkça sorulan sorular4
BST'de en düşük ortak ata işleminin zaman karmaşıklığı nedir?
Kökten yapılan yürüyüş tek bir yolu izler; bu nedenle derinliği h olan bir ağaçta O(h) zaman ve O(1) ek alan gerektirir. Dengeli bir ağaçta bu O(log n); tek bir yol şeklindeki ağaçta ise O(n) olur.
İkili arama ağacındaki LCA, ikili ağaçtaki LCA'dan nasıl farklıdır?
Sıradan bir ikili ağaçta bir değer herhangi bir yerde olabilir; bu yüzden her düğümün iki alt ağacını da ararsın ve iş miktarı O(n) olur. Bir arama ağacında iki değeri bir düğümle karşılaştırmak, her birinin hangi tarafta olduğunu söyler; bu yüzden kökten başlayarak tek bir yolu izlersin. Özyinelemeli herhangi-ağaç yöntemi bir arama ağacında da işe yarar, ancak bu bilgiyi göz ardı eder.
Bir düğüm kendi en düşük ortak atası olabilir mi?
Evet. Bir düğüm kendisinin atası sayılır; bu nedenle p, q'nun üstünde olduğunda yanıt p'dir. İki değer eşit olduğunda da aynı kural p'yi verir. Gezinme her iki durumu da ele alır: geçerli düğüm değerlerden birine eşit olur olmaz durur.
Yürüyüş neden p ve q'nun ayrıldığı ilk düğümde durur?
Bu düğümde bir değer daha küçük, diğeri ise daha büyüktür; dolayısıyla farklı alt ağaçlarda bulunurlar. Bunun altındaki her düğüm bu alt ağaçlardan yalnızca birinde yer alır ve her iki değeri birden içeremez. Ayrılma düğümü her iki değeri de içerir ve daha aşağıdaki hiçbir düğüm içermez; bu da en düşük ortak atanın tanımının ta kendisidir.
Benzer problemler
Aynı fikirleri kullanan problemler. İki üçünü çözmek bir kalıbı kalıcı hale getirir.
Python
def lowestCommonAncestor(tree, p, q):
# Kodu buraya yazınDurum 1
Durum 2
Durum 3
Girdi
tree = [20, 8, 31, 3, 12, 25, 40, -1, -1, 10, 15] p = 3 q = 15
Beklenen
8