Course Schedule
numCourses adet ders vardır ve bu dersler 0 ile numCourses-1 arasında numaralandırılmıştır. prerequisites içindeki her [a, b] çifti, a dersine başlayabilmek için önce b dersini bitirmen gerektiği anlamına gelir. Her dersi bitirebileceğin bir sıra varsa true, yoksa false döndür.
Fonksiyon
- numCoursesinteger
- kurs sayısı
- prerequisitesinteger-2d-array
- her biri kurs b'nin kurs a'dan önce geldiği anlamına gelen [a, b] çiftleri
- Döndürürboolean
- Her ders tamamlanabiliyorsa true, aksi takdirde false
Kısıtlar
1 ≤ numCourses ≤ 1051 ≤ prerequisites.length ≤ 5000- Her
[a, b]çifti için0 ≤ a, b < numCoursesgeçerlidir. - Hiçbir çift iki kez görünmez.
- Bir çift aynı dersi iki kez adlandırabilir,
[a, a]. Bu dersin önce kendisine ihtiyacı vardır, bu yüzden hiçbir zaman alınamaz.
Örnekler
- Girdi
- numCourses = 4prerequisites = [[1, 0], [2, 1], [3, 1]]
- Çıktı
- true
- Açıklama
- 0. dersin ön koşulu yok, bu yüzden önce onu alırsın. Böylece 1. dersin önü açılır ve 1. ders hem 2. hem de 3. dersin önünü açar; dolayısıyla 0, 1, 2, 3 sıralaması işe yarar.
- Girdi
- numCourses = 3prerequisites = [[0, 2], [2, 1], [1, 0]]
- Çıktı
- false
- Açıklama
- 0 numaralı ders 2'yi, 2 numaralı ders 1'i ve 1 numaralı ders 0'ı bekler. Üçü de bir döngü içinde birbirini bekler, bu yüzden hiçbirini ilk olarak alamazsın.
Gönderirken +20 gizli test
Ek soru
Her dönemde, her dersin ön koşulları daha önceki dönemlerde tamamlanmış olduğu sürece istediğin kadar ders alınabilir. Tüm dersleri kapsayan en az dönem sayısı kaçtır?
İpuçları
Tek tek açın. Her biri biraz daha fazlasını gösterir.
Her dersi bir nokta, her
[a, b]çiftini deb'dena'ya doğru bir ok olarak çiz. Bu çizimde hangi şekil, dersleri bitirmeyi imkânsız hâle getirir?Bir ok döngüsü. Bir döngüdeki her ders, aynı döngüdeki başka bir dersi bekler; bu yüzden hiçbiri ilk başlayamaz. Soru, grafın bir döngü içerip içermediğidir.
Her dersin hâlâ kaç ön koşul beklediğini say. Sayısı 0 olan derslerle bir kuyruk başlat ve bir dersi her aldığında, onu bekleyen her dersin sayısını azalt.
numCoursesdersin tümü kuyruğa ulaşmazsa bir döngü vardır.
Çözüm
Çiftleri, V = numCourses düğüm ve E = prerequisites.length kenar içeren yönlü bir grafa dönüştür; her [a, b] çifti için b → a yönünde bir ok ekle. Bu grafın döngüsü olmadığında tüm dersler tam olarak bitirilebilir. Kahn algoritması, bir öğrencinin planlama yapacağı şekilde karar verir: ön koşullarının tümü tamamlanmış bir dersi almaya devam et ve önce derslerin mi yoksa seçeneklerin mi tükendiğine bak.
Her turda tüm ücretsiz kursları al
Doğru, ama en büyük testlerde bitmiyor
Sezgi
Bir öğrencinin yapacağı gibi planla. Her turda, henüz almadığın her derse bak. Ön koşullarının tümü alınmışsa o dersi al. Bir turda hiçbir ders alınmayana kadar tekrarla. O zamana kadar tüm dersler alınmışsa yanıt doğrudur.
Hiçbir ders alınamayan bir turun neden yanlış yanıt anlamına geldiği: Böyle bir turda geriye kalan her dersin, yine geriye kalan bir ön koşulu vardır. Geride kalan herhangi bir dersten başla ve alınmamış ön koşullarından birine doğru ilerlemeyi sürdür. Adımların asla tükenmez ve ders sayısı sınırlıdır; bu yüzden daha önce ziyaret ettiğin bir derse geri dönersin. Bu bir döngüdür ve döngüdeki dersler sonsuza dek birbirini bekler.
Yöntem doğrudur, ancak her turda tüm ders çiftlerini ve tüm dersleri yeniden inceler; ayrıca bir turda yalnızca bir ders alınabilir. Her biri kendinden önceki dersi gerektiren 5,001 derslik bir zincir 5,000'den fazla tur sürer; 100,000 ders arasında bu yaklaşık 5 × 10^8 kontrol demektir ve bunların neredeyse tümü durumu değişmemiş dersler üzerinde yapılır.
Algoritma
- Her dersi alınmamış olarak işaretle.
- Alınmamış bir ön koşulu olan herhangi bir ders varsa o dersi engellenmiş olarak işaretle.
- Ne alınmış ne de engellenmiş olan her dersi al.
- Turda hiçbir ders alınmadıysa dur; aksi hâlde 2. adıma dön.
- Her ders alınmışsa true döndür.
def canFinish(numCourses, prerequisites):
taken = [False] * numCourses
count = 0
while True:
# A course is blocked while one of its prerequisites is not taken.
blocked = [False] * numCourses
for course, before in prerequisites:
if not taken[before]:
blocked[course] = True
# Take every course that is free this round.
progress = False
for c in range(numCourses):
if not taken[c] and not blocked[c]:
taken[c] = True
count += 1
progress = True
if not progress:
break
return count == numCoursesÜç durumlu derinlik öncelikli arama
Sezgi
Döngü, başladığı yere geri dönen bir yoldur. Derinlik öncelikli arama, o anda izlediği yolda hangi derslerin bulunduğunu hatırlayarak bir döngü bulur. Her derse üç durumdan birini verin: ziyaret edilmedi, mevcut yolda ve tamamlandı.
Bir dersten, oklarını izleyerek onu bekleyen derslere ilerleyin. Bir dersin üzerine geldiğinizde onu "yolda" olarak işaretleyin; o dersten çıkan her ok incelenip geri döndüğünüzde ise "tamamlandı" olarak işaretleyin. Yolda olan bir derse giden ok, bir daire çizdiğiniz anlamına gelir: false döndürün. Tamamlanmış bir derse giden ok güvenlidir; çünkü o dersten erişilebilen her şey kontrol edilmiş ve döngü içermediği doğrulanmıştır, bu yüzden o dersi atlayın. Her derse bir kez girilir ve her ok bir kez izlenir.
İki durum yeterli değildir. 0 → 1, 0 → 2, 1 → 3, 2 → 3 elmasında arama, 2 üzerinden 3. derse ikinci kez ulaşır; ancak o zamana kadar 3 tamamlanmıştır, yolda değildir ve döngü yoktur. Döngüyü yalnızca mevcut yola geri dönen bir ok kapatır.
Aramayı kendi yığınınızla ve her ders için incelenmemiş bir sonraki okun konumunu tutarak yazın. Özyinelemeli sürüm daha kısadır, ancak 5,000 dersten oluşan bir zincir 5,000 çağrı derinliğine ulaşır.
Algoritma
- Her ders için, onu bekleyen derslerin listesini oluştur.
- Ziyaret edilmemiş her ders için, onu yolda olarak işaretle ve bir yığına ekle.
- Yığının en üstüne bak. Kalan oku yoksa onu tamamlandı olarak işaretle ve yığından çıkar; aksi hâlde sıradaki oku izle.
- Ok yoldaki bir derse çıkıyorsa return false döndür. Ziyaret edilmemiş bir derse çıkıyorsa, o dersi yolda olarak işaretle ve yığına ekle.
- Tüm dersler tamamlandığında return true döndür.
def canFinish(numCourses, prerequisites):
unlocks = [[] for _ in range(numCourses)]
for course, before in prerequisites:
unlocks[before].append(course)
# 0 = not visited, 1 = on the current path, 2 = done, no cycle below it
state = [0] * numCourses
# next_edge[c] counts the edges out of c the search has already followed.
next_edge = [0] * numCourses
for start in range(numCourses):
if state[start] != 0:
continue
# A stack of our own instead of recursion: a chain of 5,000
# courses would go 5,000 calls deep.
state[start] = 1
stack = [start]
while stack:
course = stack[-1]
if next_edge[course] == len(unlocks[course]):
state[course] = 2
stack.pop()
continue
nxt = unlocks[course][next_edge[course]]
next_edge[course] += 1
if state[nxt] == 1:
return False # an edge back to the current path closes a cycle
if state[nxt] == 0:
state[nxt] = 1
stack.append(nxt)
return TrueKahn algoritması
Sezgi
İlk yaklaşımdaki turlar, değişmemiş dersleri tekrar kontrol ederek zaman kaybettirir. Bir ders yalnızca tek bir anda müsait hâle gelir: son ön koşulu alındığında. Bu nedenle her ders için hâlâ kaç ön koşulunu beklediğini, yani giriş derecesini sayın. Bir dersi aldığınızda, onu bekleyen her dersin sayısını azaltın. Sayısı 0'a düşen bir ders artık müsaittir, bu yüzden onu kuyruğa eklersiniz.
Kuyruğu en baştan sayısı 0 olan tüm derslerle başlatın, sonra kuyruk boşalana kadar dersleri kuyruktan alın. İlk örnekte 0'dan 3'e kadar olan derslerin başlangıç sayıları 0, 1, 1, 1'dir. 0'ı almak, 1 numaralı dersin sayısını 0'a düşürür; 1'i almak, 2 ve 3 numaralı derslerin sayılarını 0'a düşürür; dördü de alınır, dolayısıyla yanıt true olur. Her ders kuyruğa en fazla bir kez girer ve her çift bir sayıyı bir kez azaltır, bu nedenle işlem O(V + E) olur.
Geride kalan bir dersin neden bir döngü anlamına geldiği: a dersi alınmamışken kuyruk boşalırsa, sayısı 0'dan büyüktür; dolayısıyla ön koşullarından biri olan b dersi de alınmamıştır. Aynı durum b için de geçerlidir ve böyle devam eder. Bir dersten alınmamış bir ön koşula doğru ilerleyen yol hiç sona ermez; dolayısıyla bir dersi yeniden ziyaret eder ve bu bir döngüdür. İkinci örnekte başlangıçta hiçbir sayım 0 değildir, kuyruk boştur ve üç dersin hiçbiri alınmaz.
Tersi de geçerlidir: Bir döngüdeki ders, aynı döngüdeki başka bir dersi bekler; bu nedenle o ders alınmadan önce sayısı 0'a ulaşamaz ve bu derslerin hiçbiri ilk alınan olamaz. Dolayısıyla "her dersin alınması" ve "döngü olmaması" aynı şeydir. Ek bir avantaj olarak, derslerin kuyruktan çıkış sırası geçerli bir programdır.
Algoritma
- Her [a, b] çifti için, b'yi bekleyen dersler listesine a'yı ekleyin ve a'nın giriş derecesini 1 artırın.
- Giriş derecesi 0 olan her dersi bir kuyruğa ekleyin.
- Kuyruktan bir ders alın ve sayın. Onu bekleyen her dersin giriş derecesini azaltın ve 0'a ulaşan her birini kuyruğa ekleyin.
- Kuyruk boşaldığında, sayının
numCoursesdeğerine eşit olup olmadığını döndürün.
from collections import deque
def canFinish(numCourses, prerequisites):
# unlocks[b] lists the courses that wait for b.
# indegree[a] counts the prerequisites of a that are not taken yet.
unlocks = [[] for _ in range(numCourses)]
indegree = [0] * numCourses
for course, before in prerequisites:
unlocks[before].append(course)
indegree[course] += 1
# Every course with no prerequisites can be taken right away.
queue = deque(c for c in range(numCourses) if indegree[c] == 0)
taken = 0
while queue:
course = queue.popleft()
taken += 1
for nxt in unlocks[course]:
indegree[nxt] -= 1
if indegree[nxt] == 0:
queue.append(nxt)
# A course on a cycle never gets down to 0, so it is never taken.
return taken == numCourses
Tuzaklar ve uç durumlar
Hataların çoğu bir çiftin yönünden, fazla katı bir döngü kontrolünden veya hiçbir çiftte yer almayan derslerden kaynaklanır.
- Yönü karıştırmak.
[a, b], b'nin önce geldiği anlamına gelir; dolayısıyla ok b'den a'ya gider ve a'nın giriş derecesi artar. Listeleri bir yönde oluşturup giriş derecelerini diğer yönde saymak algoritmayı bozar. - Hiçbir çiftte yer almayan dersleri unutmak.
numCourses = 5ve tek çift[4, 3]olduğunda 0, 1 ve 2 numaralı dersler de sayılır. Kuyruğu, yalnızca bir çiftte gördüklerinle değil, giriş derecesi 0 olan her dersle başlat. - Kendi ön koşulu olan bir ders:
[2, 2]. Bu, uzunluğu bir olan bir döngüdür: giriş derecesi hiçbir zaman 0'a ulaşmaz ve yanıt false olur. - Derinlik öncelikli aramada üç durum yerine iki durum kullanmak. 0 → 1, 0 → 2, 1 → 3, 2 → 3 elmas biçiminde, yalnızca "görüldü" durumunu izlersen 3 numaralı derse iki kez ulaşılır ve bu bir döngü varmış gibi görünür. Döngüyü yalnızca mevcut yola geri dönen bir ok kapatır.
- Uzun zincirlerde özyineleme kullanmak. 5.000 derslik bir zincir 5.000 çağrı derinliğine ulaşır ve Python'un varsayılan 1.000 sınırını aşar.
- Kuyruk boşaldığında, alınan ders sayısını
numCoursesile karşılaştırmadan true döndürmek.
Sıkça sorulan sorular4
Course Schedule probleminin zaman karmaşıklığı nedir?
O(V + E); burada V ders sayısı, E ise çift sayısıdır. Kahn algoritması veya derinlik öncelikli arama kullanılır. Listeleri oluşturmak her çifti bir kez okur, her ders kuyruğa en fazla bir kez girer ve her çift bir sayacı bir kez azaltır. Listeler ve sayaçlar O(V + E) alan kaplar.
Kahn algoritmasında geriye kalan bir ders neden bir döngü olduğu anlamına gelir?
Bir ders ancak sayısı hiçbir zaman 0'a ulaşmadıysa geriye kalır; dolayısıyla ön koşullarından en az biri de geriye kalır. Bu bekleme durumunu dersten derse takip et: her adımda geriye kalan başka bir derse ulaşırsın ve ders sayısı sonlu olduğundan, yürüyüş daha önce gördüğün bir derse geri dönmek zorundadır. Bu iki ziyaret arasındaki kısım bir döngüdür.
Course Schedule için BFS mi yoksa DFS mi kullanmalısınız?
İkisi de O(V + E) sürede çalışır. Genişlik öncelikli sürüm olan Kahn algoritmasında endişelenmeniz gereken bir özyineleme derinliği yoktur ve size geçerli bir ders sırasını ücretsiz olarak verir. Üç durumlu derinlik öncelikli arama da aynı hızdadır ve döngüyü de bildirmeniz gerektiğinde doğal seçimdir; çünkü yığındaki dersler döngüyü oluşturur.
Topolojik sıralama nedir?
Yönlü bir grafın düğümlerinin, her okun ileriye doğru gösterdiği sıralaması; burada, her ön koşulun onu gerektiren dersten önce geldiği bir ders sıralaması. Grafik döngü içermiyorsa ve yalnızca o zaman böyle bir sıralama vardır; Kahn algoritmasının dersleri aldığı sıra da böyle bir sıralamadır. Course Schedule, topolojik bir sıralamanın var olup olmadığını sorar.
Benzer problemler
Aynı fikirleri kullanan problemler. İki üçünü çözmek bir kalıbı kalıcı hale getirir.
Python
def canFinish(numCourses, prerequisites):
# Kodu buraya yazınDurum 1
Durum 2
Girdi
numCourses = 4 prerequisites = [[1, 0], [2, 1], [3, 1]]
Beklenen
true