0Pricing
Coding Interview Prep · บทเรียน

ตารางเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS
  2. การเรียงลำดับเชิงทอพอโลยีด้วยลำดับหลังของ DFS
  3. ตารางเรียน I และ II
  4. องค์ประกอบเชื่อมโยงอย่างแน่นหนาด้วย Kosaraju
← กลับไปที่ Coding Interview Prep