Jump Game
nums dizisinin 0. indeksinde duruyorsun. i indeksinden 1 ile nums[i] arasında herhangi bir sayıda adım ileriye zıplayabilirsin; bu nedenle nums[i] buradan yapabileceğin en uzun zıplamayı belirtir ve 0 hareket edemeyeceğin anlamına gelir. Bir dizi zıplamayla son indekse ulaşılabiliyorsa true, aksi hâlde false döndür.
Fonksiyon
- numsinteger-array
- her dizinden yapabileceğin en uzun sıçrama
- Döndürürboolean
- 0 indeksinden başlayarak son indekse ulaşabiliyorsan true, aksi takdirde false
Kısıtlar
1 ≤ nums.length ≤ 1040 ≤ nums[i] ≤ 105- Bir atlama
nums[i]'den daha kısa olabilir; bu nedenle uzun bir atlama seni son indeksi aşmaya asla zorlamaz.
Örnekler
- Girdi
- nums = [2, 0, 3, 1, 0, 2]
- Çıktı
- true
- Açıklama
- 0. indexten 1. veya 2. indexe ulaşabilirsiniz. 1. index 0 değerini içerir ve bir çıkmazdır, ancak 2. index 3 değerini içerir ve son index olan 5. indexe ulaşır.
- Girdi
- nums = [1, 3, 0, 0, 0, 2]
- Çıktı
- false
- Açıklama
- 0. indeks yalnızca 1. indekse ilerleyebilir ve 1. indeks en fazla 4. indekse ulaşır. 2., 3. ve 4. indekslerin tümü 0 değerini içerir, bu yüzden hiçbir şey 4. indeksi geçip 5. indekse ulaşamaz.
- Girdi
- nums = [0]
- Çıktı
- true
- Açıklama
- Dizide bir öğe var, bu yüzden son indisten başlarsın ve hiç sıçrama yapman gerekmez.
Gönderirken +18 gizli test
Ek soru
Son dizine ulaşan farklı sıçrama dizilerinin sayısını 10^9+7 modunda, yine O(n) zamanda hesaplayın.
İpuçları
Tek tek açın. Her biri biraz daha fazlasını gösterir.
Bir
0, yalnızca ondan önceki hiçbir şey onun üzerinden atlayamıyorsa seni tuzağa düşürür. Bunu anlayabilmek için ondan önceki indeksler hakkında ne bilmen gerekir?iindeksine ulaşabiliyorsan, daha kısa sıçramalara izin verildiği içiniilei+nums[i]arasındaki tüm indekslere ulaşabilirsin. Bu nedenle ulaşılabilir indeksler her zaman 0 indeksinden başlayan kesintisiz bir blok oluşturur.Soldan sağa ilerle ve o bloğun sağ ucu olan
farthestdeğerini takip et. Geçerli indeksfarthestdeğerini aşarsa ona hiçbir zaman ulaşılamaz. Aksi hâlde, daha büyüksefarthestdeğerinii+nums[i]olarak genişlet. İlerleyiş dizinin tamamını geçerse son indekse ulaşılabilir.
Çözüm
Olası rotaların sayısı üstel olarak artar, bu nedenle rotaları tek tek kontrol etmek uzun dizilerde işe yaramaz. Önemli olan şu: ulaşabileceğin dizinler her zaman 0. dizinden başlayan kesintisiz bir blok oluşturur. Bu bloğun sağ ucunu gösteren tek bir sayı ihtiyacın olan her şeyi içerir ve tek bir geçiş yanıtı belirler.
Her atlamayı dene
Doğru, ama en büyük testlerde bitmiyor
Sezgi
En doğrudan fikir, bunu canlandırmaktır. 0 indeksinde dur ve sıçrayışının ulaşmana izin verdiği her iniş noktasını teker teker dene. Her iniş noktasından aynı şeyi tekrar yap. Herhangi bir dal son indekse ulaşırsa yanıt true olur. Her dal çıkmaza girerse yanıt false olur.
İlk örnekte 0 indeksinde 2 bulunur; bu yüzden 1 ve 2 indekslerini denersin. 1 indeksinde 0 bulunur, yani burası bir çıkmazdır; bu yüzden geri dönüp 2 indeksini denersin. 2 indeksinde 3 bulunur ve 5 indeksine, yani son indekse ulaşır; arama true sonucuyla sona erer.
Arama doğrudur çünkü her rotaya bakar. Sorun da budur: Daha önce incelediği bir indeksi asla hatırlamaz, bu yüzden o indekse ulaşan her rota için aynı indeksi yeniden inceler. Yanıt false olduğunda her rotayı elemesi gerekir. [4, 3, 2, 1, 0, 5] dizisinde, 0'dan önceki her indeks 0'a ulaşabilir; bu da 0'a giden 8 farklı rota oluşturur. Bu tür 30 indeks olduğunda 500 milyondan fazla rota vardır ve en büyük testlerde 10.000 eleman bulunur. Böyle uzun bir rota, bazı dillerde çağrı yığınını da taşırır: Python varsayılan olarak 1.000 iç içe çağrıda durur.
Algoritma
- Şu soruyu yanıtlayan bir yardımcı
reach(i)yazın:iindeksinden son indekse ulaşabilir misiniz? ison indekse eşitsetruedöndürün.- Aksi takdirde
i+1ilemin(i+nums[i], n-1)arasındaki her iniş noktasınınextdeneyin vereach(next)truedöndürür döndürmeztruedöndürün. - Hiçbir iniş noktası işe yaramazsa
falsedöndürün. - Yanıt
reach(0)olur.
def canJump(nums):
last = len(nums) - 1
def reach(i):
# Can you get from index i to the last index?
if i == last:
return True
for nxt in range(i + 1, min(i + nums[i], last) + 1):
if reach(nxt):
return True
return False
return reach(0)Hangi indekslerin işlemi tamamlayabileceğini hatırla
Doğru, ama en büyük testlerde bitmiyor
Sezgi
Yukarıdaki arama, “j indeksi sona ulaşabilir mi?” sorusunu tekrar tekrar soruyor. j için yanıt hiç değişmez; bu yüzden bir kez hesaplayıp saklayın. Bir indeksten son indekse ulaşabiliyorsanız, bu indekse iyi deyin. Son indeks iyidir. i dışındaki herhangi bir indeks, i+1 ile i+nums[i] arasında ulaşabileceği en az bir indeks iyiyse iyidir.
Her indeks yalnızca sağındaki indekslere bağlıdır; bu nedenle good tablosunu sağdan sola doldurun. İlk örnekte 5. indeks iyidir. 4. indeksin değeri 0 olduğundan iyi değildir. 3. indeks yalnızca 4. indekse ulaşır: iyi değildir. 2. indeks 3., 4. ve 5. indekslere ulaşır; 5. indeks iyi olduğundan 2. indeks de iyidir. 1. indeksin değeri 0: iyi değildir. 0. indeks 1. ve 2. indekslere ulaşır; 2. indeks iyi olduğundan yanıt true olur.
Artık her indeks için karar yalnızca bir kez veriliyor, ancak bu kararı verirken hâlâ n hücreye kadar tarama yapılabilir. [9998, 9997, …, 1, 0, 7] dizisinde her indeks 0'a ulaşabilir, ancak onun ötesine geçemez; bu nedenle her biri kendi aralığının tamamını tarar ve iyi bir indeks bulamaz. 10.000 eleman için bu yaklaşık 5 × 10^7 kontrol demektir ve en büyük testler de bunun gibi oluşturulmuştur. İş miktarı uzunluğun karesiyle birlikte büyür, bu yüzden bu testlerde süre sınırını aşar.
Algoritma
nuzunluğunda bir boole dizisigoodoluştur vegood[n-1]değerini true olarak ayarla.ideğerinin-2'den 0'a kadar azaltarak ilerle.jdeğerinii+1'denmin(i+nums[i], n-1)değerine kadar tara.good[j]değerlerinden herhangi biri true isegood[i]değerini true olarak ayarla ve taramayı durdur.good[0]değerini döndür.
def canJump(nums):
n = len(nums)
good = [False] * n # good[i]: from i you can reach the last index
good[n - 1] = True
for i in range(n - 2, -1, -1):
for j in range(i + 1, min(i + nums[i], n - 1) + 1):
if good[j]:
good[i] = True
break
return good[0]Ulaşılabilen en uzak dizin konumunu takip et
Sezgi
Güzergâhlara değil, hangi indekslere ulaşabildiğine bak. i indeksinden i+1 ile i+nums[i] arasındaki herhangi bir indekse, arada boşluk olmadan ulaşabilirsin. Dolayısıyla i indeksine ulaşılabiliyorsa, i+nums[i] değerine kadar olan her indekse de ulaşılabilir. Yalnızca 0 indeksinden başlayıp bu aralıkları sürekli genişlet. Her yeni aralık, elindeki bloğun içinde başladığından, ulaşılabilir indeksler her zaman kesintisiz tek bir blok oluşturur: [0, farthest].
Bu yüzden tek bir sayı yeterlidir. i değerini soldan sağa ilerlet. i ≤ farthest olduğu sürece i indeksine ulaşılabilir; bu nedenle farthest değerini max(farthest, i+nums[i]) değerine genişlet. i herhangi bir noktada farthest değerini geçerse, ulaşılabilir hiçbir indeks i indeksine sıçrayamaz. Blok bu boşluğu aşarak genişleyemez; dolayısıyla bloğun sağındaki hiçbir indekse, son indeks de dahil, ulaşılamaz. İlerleme boşluk olmadan sona ulaşırsa son indekse ulaşılabilir.
İkinci örnekte, farthest önce 0, 0 indeksinden sonra 1, 1 indeksinden sonra da 4 olur. 2, 3 ve 4 indekslerinde 0 bulunur ve değer 4 olarak kalır. 5 indeksi 4'ü geçtiğinden cevap false olur. İlk örnekte, 2 indeksi farthest değerini 5'e çıkarır ve hiçbir indeks bu değeri geçmez; dolayısıyla cevap true olur.
Yalnızca en uzak erişim mesafesini tutmak neden güvenlidir? Hiçbir sıçrama için kesin karar vermezsin. Blok, herhangi bir güzergâhın ulaşabileceği tüm indeksleri içerir ve daha kısa mesafeli her iniş noktası da bu bloğun içinde kalır. Sağ uç dışındaki her şeyi göz ardı etmek hiçbir bilgi kaybına yol açmaz.
Algoritma
farthest = 0olarak ayarla.- Soldan sağa her
iindeksi için: eğeri > farthestisefalsedöndür. - Aksi hâlde
farthest = max(farthest, i+nums[i])olarak ayarla. - Döngü biterse her indekse ulaşılabilmiştir, bu nedenle
truedöndür.
def canJump(nums):
farthest = 0 # every index up to farthest can be reached
for i, jump in enumerate(nums):
if i > farthest:
return False # nothing reachable jumps to i
farthest = max(farthest, i + jump)
return True
Tuzaklar ve uç durumlar
Yanlış yanıtların çoğu, nums[i] değerini tek olası sıçrama olarak okumaktan veya döngünün içindeki iki kontrolün sırasını karıştırmaktan kaynaklanır.
- Her zaman tam olarak
nums[i]kadar adım atmak ya da her zaman en uzun sıçramayı yapmak.[2, 5, 0, 0]dizisinde, 0. indeksten yapılan tam sıçrama 0 değerine ulaşırken, 1. indekse yapılan 1 adımlık sıçrama sona ulaşır. - Bir 0 görür görmez
falsedöndürmek. 0, yalnızca ondan önceki hiçbir sıçrama onu aşamıyorsa önemlidir:[2, 0, 1]dizisi 0'ın üzerinden sıçrar ve yanıttrueolur. i > farthestkontrolünden öncefarthestdeğerini güncellemek. Ulaşamayacağın bir indeks aralığı genişletmemelidir; bu yüzden önce kontrol et, sonra güncelle.- Tek elemanlı bir diziyi başarısızlık olarak değerlendirmek. Zaten son indekstesin; bu nedenle o eleman 0 olsa bile yanıt
trueolur. - Uzun dizilerde özyineleme kullanmak. Bir rota 10,000 sıçrama uzunluğunda olabilir ve bu, bazı dillerde çağrı yığınını taşırır. Tek geçişte özyineleme kullanılmaz.
Sıkça sorulan sorular4
Jump Game’in zaman karmaşıklığı nedir?
En uzağa erişim geçişi her dizini bir kez ziyaret eder; bu nedenle O(n) zamanda ve O(1) ek alanla çalışır. Tablo yaklaşımı en kötü durumda O(n²) karmaşıklığındadır ve her rotayı denemek üstel zaman alır.
Neden açgözlü yaklaşım Jump Game için işe yarar?
Daha kısa sıçramalara izin verildiğinden, i dizinine ulaşmak, i+nums[i] dahil olmak üzere ona kadarki tüm dizinlere ulaşabileceğin anlamına gelir. Bu aralıklar her zaman daha önce ulaşılan bölümle örtüşür; dolayısıyla ulaşılabilir dizinler 0'dan başlayan tek bir blok oluşturur. Açgözlü geçiş yalnızca bu bloğun sağ ucunu izler; bu uç bloğun tamamını tanımladığı için işe yarayabilecek hiçbir yolu gözden çıkarmaz.
Jump Game bir dinamik programlama problemi midir?
Dinamik programlamayla çözülebilir: tabloyu sağdan sola doldurarak, iniş noktalarından biri iyi olduğunda her indeksi iyi olarak işaretleyin. Bunun maliyeti O(n²) olur. Yalnızca en soldaki iyi indeksin önemli olduğunu fark edin; çünkü iyi bir indekse ulaşabilen her indeks, en soldakine de ulaşır. Yalnızca bu indeksi, goal'ı tutun ve i+nums[i] ≥ goal olduğunda onu i olarak güncelleyin. Yanıt, goal'ın 0'da bitip bitmediğidir; bu, açgözlü yöntemi yansıtan O(n)'lik bir geçiştir.
Minimum sıçrama sayısını nasıl bulursun?
Aynı en uzağa erişme fikrini katmanlar hâlinde kullanın. Mevcut sıçrama sayısıyla ulaşabileceğiniz bloğun sonunu ve bir sonraki sıçramayla ulaşılabilecek en uzak dizini takip edin. i mevcut bloğun sonunu geçtiğinde bir sıçrama daha yapmanız gerekir ve sonraki blok o en uzak dizinde biter. Bu hâlâ tek bir O(n) geçişidir.
Benzer problemler
Aynı fikirleri kullanan problemler. İki üçünü çözmek bir kalıbı kalıcı hale getirir.
Python
def canJump(nums):
# Kodu buraya yazınDurum 1
Durum 2
Durum 3
Girdi
nums = [2, 0, 3, 1, 0, 2]
Beklenen
true