ตารางเรียน I และ II
จำลองวิชาบังคับก่อนเป็นกราฟมีทิศทาง และใช้การเรียงลำดับเชิงทอพอโลยีเพื่อตรวจสอบว่าสามารถเรียนครบทุกวิชาได้หรือไม่และควรเรียนตามลำดับใด
ตารางเรียน I และ II เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
ภาพรวมโจทย์
ตารางเรียน I (LeetCode 207): กำหนดรายวิชา n วิชาและรายการคู่ prerequisites [a, b] ซึ่งหมายความว่า “ต้องเรียน b ก่อน a” ให้พิจารณาว่าสามารถเรียนจบทุกวิชาได้หรือไม่ ตารางเรียน II (LeetCode 210): ให้คืนลำดับจริงสำหรับการเรียนวิชา หรือคืนอาร์เรย์ว่างหากไม่สามารถทำได้ ทั้งสองโจทย์ลดรูปเป็นการเรียงลำดับเชิงทอพอโลยีบนกราฟมีทิศทางที่สิ่งที่ต้องเรียนก่อนถูกแทนด้วยเส้นเชื่อม
การสร้างแบบจำลองกราฟ
สร้างกราฟมีทิศทาง: สำหรับคู่สิ่งที่ต้องเรียนก่อนแต่ละคู่ [a, b] ให้เพิ่มเส้นเชื่อม b → a (“b ต้องมาก่อน a” หมายความว่า b นำไปสู่ a) คำนวณองศาขาเข้าสำหรับแต่ละวิชา วิชาที่มีองศาขาเข้าเป็น 0 ไม่มีสิ่งที่ต้องเรียนก่อนและสามารถเรียนได้ทันที โจทย์นี้แก้ได้ก็ต่อเมื่อกราฟไม่มีวัฏจักร (ไม่มีการพึ่งพากันเป็นวงกลม)
from collections import defaultdict
def build_graph(n, prerequisites):
graph = defaultdict(list)
in_degree = [0] * n
for a, b in prerequisites: # b must come before a
graph[b].append(a)
in_degree[a] += 1
return graph, in_degree
graph, ind = build_graph(4, [[1,0],[2,0],[3,1],[3,2]])
print('In-degrees:', ind) # [0, 1, 1, 2]
print('Graph edges:', dict(graph))ตารางเรียน I: วิธีแก้ด้วยอัลกอริทึมคาห์น
ใช้อัลกอริทึมคาห์น หากจำนวนวิชาที่ประมวลผลแล้วเท่ากับ n แสดงว่าสามารถเรียนจบได้ทุกวิชา มิฉะนั้น การพึ่งพากันเป็นวงกลมจะทำให้เรียนจบไม่ได้
from collections import deque, defaultdict
def canFinish(numCourses, prerequisites):
graph = defaultdict(list)
in_degree = [0] * numCourses
for a, b in prerequisites:
graph[b].append(a)
in_degree[a] += 1
queue = deque(i for i in range(numCourses) if in_degree[i] == 0)
count = 0
while queue:
course = queue.popleft()
count += 1
for nxt in graph[course]:
in_degree[nxt] -= 1
if in_degree[nxt] == 0:
queue.append(nxt)
return count == numCourses
print(canFinish(2, [[1,0]])) # True
print(canFinish(2, [[1,0],[0,1]])) # Falseตารางเรียน II: คืนลำดับ
ทำเช่นเดียวกับตารางเรียน I แต่ให้รวบรวมลำดับของวิชาไปพร้อมกับการประมวลผล คืนลำดับนั้นหากมีวิชาครบทั้งหมด มิฉะนั้นให้คืนรายการว่าง
from collections import deque, defaultdict
def findOrder(numCourses, prerequisites):
graph = defaultdict(list)
in_degree = [0] * numCourses
for a, b in prerequisites:
graph[b].append(a)
in_degree[a] += 1
queue = deque(i for i in range(numCourses) if in_degree[i] == 0)
order = []
while queue:
course = queue.popleft()
order.append(course)
for nxt in graph[course]:
in_degree[nxt] -= 1
if in_degree[nxt] == 0:
queue.append(nxt)
return order if len(order) == numCourses else []
print(findOrder(4, [[1,0],[2,0],[3,1],[3,2]]))ตารางเรียนด้วย DFS
อีกทางเลือกหนึ่งคือใช้ DFS เพื่อตรวจจับวัฏจักร วิชาจะมีสามสถานะ ได้แก่ ยังไม่เยี่ยมชม (0), กำลังประมวลผล (1) และเสร็จแล้ว (2) หากระหว่าง DFS พบวิชาที่กำลังประมวลผลอยู่ แสดงว่ามีวัฏจักร วิธีนี้ให้ผลเทียบเท่ากับอัลกอริทึมคาห์น แต่ใช้ DFS แบบเรียกซ้ำ
from collections import defaultdict
def canFinish_dfs(numCourses, prerequisites):
graph = defaultdict(list)
for a, b in prerequisites:
graph[b].append(a)
# 0=unvisited, 1=in-progress, 2=done
state = [0] * numCourses
def has_cycle(course):
if state[course] == 1: return True # back edge
if state[course] == 2: return False # already cleared
state[course] = 1
for nxt in graph[course]:
if has_cycle(nxt):
return True
state[course] = 2
return False
return not any(has_cycle(i) for i in range(numCourses))
print(canFinish_dfs(2, [[1,0]])) # True
print(canFinish_dfs(2, [[1,0],[0,1]])) # Falseเหตุใดทิศทางของเส้นเชื่อมจึงสำคัญ
ข้อผิดพลาดที่พบบ่อยคือกลับทิศทางของเส้นเชื่อม: หากสิ่งที่ต้องเรียนก่อนคือ [a, b] ซึ่งหมายความว่า “b มาก่อน a” ให้เพิ่มเส้นเชื่อม b → a ไม่ใช่ a → b ทิศทางของเส้นเชื่อมต้องสะท้อนการไหลของการพึ่งพา นั่นคือลูกศรชี้จากสิ่งที่ต้องทำก่อน ไปยังสิ่งที่พึ่งพาสิ่งนั้น หากใช้ทิศทางผิด การตรวจจับวัฏจักรและการจัดลำดับจะย้อนกลับด้าน ทำให้ได้ผลลัพธ์ไม่ถูกต้องในโจทย์ที่มีการพึ่งพาหลายรายการ
ตารางเรียน III: รูปแบบการเลือกแบบละโมบ
ตารางเรียน III (LeetCode 630) เป็นโจทย์คนละแบบ: วิชามีระยะเวลาและกำหนดส่ง และต้องการเพิ่มจำนวนวิชาที่เรียนให้ได้มากที่สุด วิธีแก้คือใช้การเลือกแบบละโมบร่วมกับแมกซ์ฮีป โดยเลือกวิชาที่มีกำหนดส่งช้าที่สุดก่อนเสมอ หากการเพิ่มวิชาหนึ่งทำให้เกินกำหนดส่ง ให้แทนที่วิชานั้นด้วยวิชาที่ใช้เวลานานที่สุดที่เลือกไว้ก่อนหน้า (หากวิชานั้นใช้เวลานานกว่า) นี่เป็นโจทย์การเลือกแบบละโมบ ไม่ใช่โจทย์การเรียงลำดับเชิงทอพอโลยี แสดงให้เห็นว่าการอ่านโจทย์อย่างละเอียดมีความสำคัญ
การจัดการโหนดที่แยกเดี่ยว
วิชาที่ไม่มีทั้งสิ่งที่ต้องเรียนก่อนและวิชาอื่นที่พึ่งพาวิชาเหล่านั้นคือ โหนดที่แยกเดี่ยว ซึ่งมีองศาขาเข้าเป็น 0 และไม่มีเส้นเชื่อมขาออก อัลกอริทึมคาห์นจัดการโหนดเหล่านี้ได้อย่างถูกต้อง โดยจะนำโหนดเหล่านี้เข้าคิวและประมวลผลทันที อย่าลืมกำหนดค่าองศาขาเข้าเริ่มต้นให้โหนด ALL ตั้งแต่ 0 ถึง n-1 แม้แต่โหนดที่ไม่ปรากฏในรายการสิ่งที่ต้องเรียนก่อน มิฉะนั้นโหนดเหล่านั้นจะถูกข้ามไป
# Example: 4 courses, but only courses 0 and 1 have a prerequisite relationship
# Courses 2 and 3 are isolated - they should appear in the output
from collections import deque, defaultdict
def findOrder_isolated(numCourses, prerequisites):
graph = defaultdict(list)
in_degree = [0] * numCourses # initialise ALL nodes
for a, b in prerequisites:
graph[b].append(a)
in_degree[a] += 1
queue = deque(i for i in range(numCourses) if in_degree[i] == 0)
order = []
while queue:
c = queue.popleft(); order.append(c)
for nxt in graph[c]:
in_degree[nxt] -= 1
if in_degree[nxt] == 0: queue.append(nxt)
return order if len(order) == numCourses else []
print(findOrder_isolated(4, [[1,0]])) # [0,1,2,3] or [2,3,0,1] etc.เวลาเรียนจบรายวิชาแบบขนาน
รายวิชาแบบขนาน II: จงหาจำนวนภาคการศึกษาขั้นต่ำในการเรียนให้ครบทุกวิชา เมื่ออนุญาตให้เรียนได้ไม่เกิน k วิชาต่อภาคการศึกษา และต้องเคารพสิ่งที่ต้องเรียนก่อน โจทย์นี้ต้องใช้อัลกอริทึมคาห์นที่ประมวลผลทีละระดับร่วมกับบิตมาสก์ DP เพื่อจัดการข้อจำกัดในการเลือก k วิชา เป็นโจทย์ที่ยากขึ้นอย่างมาก เพราะผสานการเรียงลำดับเชิงทอพอโลยีเข้ากับบิตมาสก์ DP
กลยุทธ์การสื่อสารในการสัมภาษณ์
เมื่อต้องเจอโจทย์ประเภทตารางเรียนในการสัมภาษณ์: (1) ระบุ ทันทีว่าเป็นโจทย์การเรียงลำดับเชิงทอพอโลยี / การตรวจจับวัฏจักร (2) สร้างแบบจำลอง กราฟโดยทำความเข้าใจว่าปลายทางของเส้นเชื่อมชี้ไปทางใด (3) เลือก อัลกอริทึมคาห์น (BFS) เพื่อความเรียบง่าย หรือ DFS หากคุ้นเคยมากกว่า (4) จัดการ กรณีที่มีวัฏจักรอย่างชัดเจน (5) ระบุ ความซับซ้อนด้านเวลา O(V+E) แนวทางที่เป็นระบบนี้แสดงให้เห็นทักษะการแก้ปัญหาอย่างมีขั้นตอน
การทดสอบอย่างครอบคลุม
ทดสอบวิธีแก้ทั้งสองแบบกับอินพุตหลากหลายรูปแบบเพื่อยืนยันความถูกต้อง วิธีของอัลกอริทึมคาห์นรองรับลำดับที่ถูกต้องได้หลายแบบอย่างยืดหยุ่น เพราะลำดับเชิงทอพอโลยีใด ๆ ที่ถูกต้องก็ยอมรับเป็นคำตอบของตารางเรียน II ได้
from collections import deque, defaultdict
def findOrder(numCourses, prerequisites):
graph = defaultdict(list)
in_degree = [0] * numCourses
for a, b in prerequisites:
graph[b].append(a)
in_degree[a] += 1
queue = deque(i for i in range(numCourses) if in_degree[i] == 0)
order = []
while queue:
c = queue.popleft(); order.append(c)
for nxt in graph[c]:
in_degree[nxt] -= 1
if in_degree[nxt] == 0: queue.append(nxt)
return order if len(order) == numCourses else []
print(findOrder(1, [])) # [0]
print(findOrder(2, [[0,1]])) # [1, 0]
print(findOrder(3, [[1,0],[2,1]])) # [0, 1, 2]
print(findOrder(3, [[1,0],[0,1]])) # [] cycleตรวจสอบอย่างรวดเร็ว
ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์เขียนโปรแกรมจากบทเรียนนี้
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า ตารางเรียน I และ II ต่างก็ใช้การเรียงลำดับเชิงทอพอโลยี โดยมีเส้นเชื่อม b → a สำหรับสิ่งที่ต้องเรียนก่อน [a, b], ตารางเรียน I เพียงตรวจว่า len(order) == n ส่วนตารางเรียน II จะคืนลำดับออกมาเอง และ การตรวจจับวัฏจักรด้วย DFS ที่ใช้สามสถานะเป็นทางเลือกที่ถูกต้องแทนวิธี BFS ของอัลกอริทึมคาห์น ต่อไปเราจะศึกษาอัลกอริทึมโคซาราจูสำหรับองค์ประกอบที่เชื่อมโยงอย่างแน่นหนา
คำถามที่พบบ่อย
บทเรียน “ตารางเรียน I และ II” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ตารางเรียน I และ II” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ตารางเรียน I และ II”
จำลองวิชาบังคับก่อนเป็นกราฟมีทิศทาง และใช้การเรียงลำดับเชิงทอพอโลยีเพื่อตรวจสอบว่าสามารถเรียนครบทุกวิชาได้หรือไม่และควรเรียนตามลำดับใด คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “ตารางเรียน I และ II” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม
ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS
- การเรียงลำดับเชิงทอพอโลยีด้วยลำดับหลังของ DFS
- ตารางเรียน I และ II
- องค์ประกอบเชื่อมโยงอย่างแน่นหนาด้วย Kosaraju