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

การจัดตารางช่วงเวลาและการรวมช่วง

แก้ปัญหาห้องประชุมและช่วงเวลาที่ไม่ทับซ้อนด้วยการเรียงตามเวลาสิ้นสุด และรวมช่วงเวลาด้วยการเรียงตามเวลาเริ่มต้น

การจัดตารางช่วงเวลาและการรวมช่วง เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

ภาพรวมปัญหาช่วงเวลา

ปัญหาช่วงเวลาปรากฏอยู่เสมอในการสัมภาษณ์เกี่ยวกับการจัดตาราง การจัดการปฏิทิน และการจัดสรรทรัพยากร รูปแบบสำคัญมีดังนี้: รวมช่วงเวลาที่ทับซ้อนกัน, นับจำนวนห้องประชุมขั้นต่ำ, หาชุดช่วงเวลาที่ไม่ทับซ้อนกันที่มีจำนวนมากที่สุด และ แทรกช่วงเวลาใหม่ ปัญหาช่วงเวลาส่วนใหญ่เริ่มต้นด้วยขั้นตอนเดียวกัน: sort ช่วงเวลาตามเวลาเริ่มต้น (หรือตามเวลา end ขึ้นอยู่กับปัญหา) การเลือกคีย์สำหรับเรียงลำดับให้ถูกต้องมักเป็นส่วนที่ยากที่สุด

# Intervals: each = [start, end] (inclusive or exclusive by problem)
# Example:
intervals = [[1,3],[2,6],[8,10],[15,18]]
# Sorted by start (already sorted here)
# Visually:
# [1,3]    |-|
# [2,6]      |---|
# [8,10]             |--|
# [15,18]                    |---|
print('Intervals ready for analysis')

รวมช่วงเวลาที่ทับซ้อนกัน

การรวมช่วงเวลา (LeetCode 56): เมื่อกำหนดรายการช่วงเวลา ให้รวมช่วงเวลาที่ทับซ้อนกันทั้งหมดเข้าด้วยกัน อัลกอริทึม: ใช้ sort ตามเวลาเริ่มต้น เดินผ่านรายการที่เรียงลำดับแล้ว หากช่วงเวลาปัจจุบันทับซ้อนกับช่วงเวลาที่รวมล่าสุด (เวลาเริ่มต้นของช่วงปัจจุบัน ≤ เวลา end ของช่วงที่รวมล่าสุด) ให้ขยายค่า end ของช่วงที่รวมล่าสุดเป็นค่าสูงสุดของปลายช่วงทั้งสอง มิฉะนั้น ให้ append ช่วงเวลาปัจจุบันเป็นช่วงเวลาที่รวมใหม่ เวลา: O(n log n) สำหรับการเรียงลำดับ และ O(n) สำหรับการรวม

def merge_intervals(intervals):
    intervals.sort(key=lambda x: x[0])  # sort by start
    merged = [intervals[0]]
    for start, end in intervals[1:]:
        last_end = merged[-1][1]
        if start <= last_end:
            # Overlapping: extend the last interval
            merged[-1][1] = max(last_end, end)
        else:
            # Non-overlapping: add as new interval
            merged.append([start, end])
    return merged

print(merge_intervals([[1,3],[2,6],[8,10],[15,18]]))
# [[1,6],[8,10],[15,18]]
print(merge_intervals([[1,4],[4,5]]))
# [[1,5]] (touching intervals merge)

แทรกช่วงเวลา

การแทรกช่วงเวลา (LeetCode 57): เมื่อกำหนดรายการช่วงเวลาที่เรียงลำดับแล้วและไม่ทับซ้อนกัน ให้แทรกช่วงเวลาใหม่แล้วรวมช่วงเวลาอีกครั้ง เดินผ่านสามระยะ: (1) เพิ่มช่วงเวลาทั้งหมดที่มีค่า end อยู่ก่อนเวลาเริ่มต้นของช่วงใหม่ (2) รวมช่วงเวลาทั้งหมดที่ทับซ้อนกับช่วงเวลาใหม่ (ขยายขอบเขตของช่วงเวลาใหม่) (3) เพิ่มช่วงเวลาที่เหลือทั้งหมด ขั้นตอนนี้ใช้การเดินผ่านรายการเพียงครั้งเดียวในเวลา O(n) หลังจากการเรียงลำดับในเวลา O(n log n) (ซึ่งทำเสร็จแล้วในปัญหานี้)

def insert_interval(intervals, new_interval):
    result = []
    i = 0
    n = len(intervals)
    # Phase 1: intervals before new_interval
    while i < n and intervals[i][1] < new_interval[0]:
        result.append(intervals[i])
        i += 1
    # Phase 2: merge overlapping intervals
    while i < n and intervals[i][0] <= new_interval[1]:
        new_interval[0] = min(new_interval[0], intervals[i][0])
        new_interval[1] = max(new_interval[1], intervals[i][1])
        i += 1
    result.append(new_interval)
    # Phase 3: remaining intervals
    while i < n:
        result.append(intervals[i])
        i += 1
    return result

print(insert_interval([[1,3],[6,9]], [2,5]))  # [[1,5],[6,9]]
print(insert_interval([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8]))
# [[1,2],[3,10],[12,16]]

ห้องประชุม I: เข้าร่วมการประชุมทั้งหมดได้หรือไม่

ห้องประชุม I (LeetCode 252): เมื่อกำหนดช่วงเวลาการประชุม ให้พิจารณาว่าบุคคลหนึ่งสามารถเข้าร่วมการประชุมทั้งหมดได้หรือไม่ เรียงลำดับตามเวลาเริ่มต้น หากการประชุมใดเริ่มก่อนที่การประชุมก่อนหน้าจะสิ้นสุด แสดงว่าการประชุมทั้งสองทับซ้อนกัน นี่เป็นการตรวจสอบช่วงเวลาที่ง่ายที่สุด — ใช้เวลารวม O(n log n) แนวคิดสำคัญคือ หลังจากเรียงลำดับแล้ว คุณเพียงเปรียบเทียบคู่ที่อยู่ติดกันเท่านั้น

def can_attend_meetings(intervals):
    intervals.sort(key=lambda x: x[0])
    for i in range(1, len(intervals)):
        # Current meeting starts before previous ends?
        if intervals[i][0] < intervals[i-1][1]:
            return False
    return True

print(can_attend_meetings([[0,30],[5,10],[15,20]]))  # False (0,30 overlaps 5,10)
print(can_attend_meetings([[7,10],[2,4]]))           # True (4 < 7, no overlap)

ห้องประชุม II: จำนวนห้องขั้นต่ำ

ห้องประชุม II (LeetCode 253): หาจำนวนห้องประชุมขั้นต่ำที่ต้องใช้เพื่อจัดการประชุมทั้งหมดในเวลาเดียวกัน ใช้ มินฮีป เพื่อติดตามห้องที่มีเวลาสิ้นสุดเร็วที่สุด เรียงลำดับการประชุมตามเวลาเริ่มต้น สำหรับการประชุมใหม่แต่ละรายการ: หากเริ่มหลังเวลา end ของห้องที่สิ้นสุดเร็วที่สุด ให้ใช้ห้องนั้นซ้ำ (ใช้ pop แล้วใส่กลับเข้าไป) มิฉะนั้น ให้เปิดห้องใหม่ เมื่อสิ้นสุดแล้ว ขนาดของฮีปจะเท่ากับจำนวนห้องที่ต้องใช้

import heapq

def min_meeting_rooms(intervals):
    if not intervals: return 0
    intervals.sort(key=lambda x: x[0])  # sort by start
    heap = []  # min-heap of end times
    for start, end in intervals:
        if heap and heap[0] <= start:
            heapq.heapreplace(heap, end)  # reuse earliest-ending room
        else:
            heapq.heappush(heap, end)     # open a new room
    return len(heap)

print(min_meeting_rooms([[0,30],[5,10],[15,20]]))  # 2
print(min_meeting_rooms([[7,10],[2,4]]))           # 1
print(min_meeting_rooms([[9,10],[4,9],[4,17]]))    # 2

ทางเลือกเส้นกวาดสำหรับนับจำนวนห้อง

อีกแนวทางหนึ่งที่มีความซับซ้อน O(n log n) คือ เส้นกวาด สร้างเหตุการณ์สำหรับจุดเริ่มต้นของแต่ละช่วงเวลา (+1) และจุด end (-1) เรียงลำดับเหตุการณ์ทั้งหมดตาม time (หากมีเวลาเท่ากัน ให้จัด end ไว้ก่อนจุดเริ่มต้นเมื่อต้องการไม่รวมปลายช่วง) กวาดจากซ้ายไปขวา โดยรักษาจำนวนนับสะสมของการประชุมที่กำลังดำเนินอยู่ ค่าสูงสุดของจำนวนนี้คือจำนวนห้องขั้นต่ำที่ต้องใช้ วิธีนี้เข้าใจได้ง่ายกว่าสำหรับบางคน และสามารถนำไปใช้กับปัญหาการนับช่วงเวลาแบบอื่นได้ด้วย

def min_rooms_sweep(intervals):
    events = []
    for start, end in intervals:
        events.append((start, 1))   # meeting starts
        events.append((end, -1))    # meeting ends
    # Sort: same time → end (-1) before start (1) if exclusive
    events.sort(key=lambda x: (x[0], x[1]))
    max_rooms = current = 0
    for _, delta in events:
        current += delta
        max_rooms = max(max_rooms, current)
    return max_rooms

print(min_rooms_sweep([[0,30],[5,10],[15,20]]))  # 2
print(min_rooms_sweep([[1,5],[2,6],[3,7]]))       # 3 (all overlap at t=3)

ช่วงเวลาที่ไม่ทับซ้อนกัน: การเลือกจำนวนมากที่สุด

ช่วงเวลาที่ไม่ทับซ้อนกัน (LeetCode 435): หาจำนวนช่วงเวลาขั้นต่ำที่ต้อง remove เพื่อให้ช่วงเวลาที่เหลือไม่ทับซ้อนกัน ปัญหานี้เทียบเท่ากับการหาจำนวน ช่วงเวลาที่ไม่ทับซ้อนกันสูงสุด (การเลือกกิจกรรม) แล้วคืนค่าช่วงเวลาที่เหลือเป็นรายการที่ต้องลบออก ให้เรียงลำดับตาม end time: เลือกเก็บช่วงเวลาที่สิ้นสุดเร็วที่สุดแบบละโมบ (เพื่อเพิ่มพื้นที่ให้ช่วงเวลาในอนาคต) เมื่อช่วงเวลาถัดไปทับซ้อน ให้ discard ช่วงเวลานั้น (นับการลบออกหนึ่งครั้ง)

def erase_overlap_intervals(intervals):
    if not intervals: return 0
    intervals.sort(key=lambda x: x[1])  # sort by END time
    removals = 0
    last_end = float('-inf')
    for start, end in intervals:
        if start >= last_end:
            last_end = end  # keep this interval
        else:
            removals += 1   # remove this interval (it overlaps)
    return removals

print(erase_overlap_intervals([[1,2],[2,3],[3,4],[1,3]]))  # 1 (remove [1,3])
print(erase_overlap_intervals([[1,2],[1,2],[1,2]]))        # 2
print(erase_overlap_intervals([[1,2],[2,3]]))              # 0 (no overlap)

เหตุใดจึงเรียงตามเวลาสิ้นสุด ไม่ใช่เวลาเริ่มต้น

สำหรับปัญหาการเลือกกิจกรรม (ชุดกิจกรรมที่ไม่ซ้อนทับกันซึ่งมีขนาดใหญ่ที่สุด) การเรียงตาม เวลาสิ้นสุด เป็นวิธีที่พิสูจน์ได้ว่าเหมาะสมที่สุด สัญชาตญาณคือ กิจกรรมที่สิ้นสุดเร็วจะเหลือพื้นที่สำหรับกิจกรรมในอนาคตมากกว่า หากเราเรียงตามเวลาเริ่มต้น เราอาจเลือกกิจกรรมที่เริ่มเร็วแต่ใช้เวลานานมาก ซึ่งขัดขวางกิจกรรมที่สั้นกว่าและเริ่มภายหลังหลายกิจกรรม การพิสูจน์ด้วยการสลับ: หากคำตอบที่เหมาะสมที่สุดเลือกกิจกรรม A แทน G ซึ่งสิ้นสุดเร็วที่สุด ให้สลับ A เป็น G — G ไม่ได้สิ้นสุดช้ากว่า ดังนั้นจึงไม่ขัดแย้งกับกิจกรรมใดที่ A ไม่ขัดแย้ง

# Counterexample for sorting by START time:
# [[1,10],[2,3],[4,5]] — sorted by start: [1,10],[2,3],[4,5]
# Sort-by-start greedy keeps [1,10], can't add [2,3] or [4,5] (all overlap [1,10])
# Selects: 1 interval

# Sort-by-end greedy:
# [[2,3],[4,5],[1,10]] — sorted by end
# Keep [2,3] (end=3), then [4,5] (start=4 >= 3, keep), then [1,10] (start=1 < 5, skip)
# Selects: 2 intervals — OPTIMAL

intervals = [[1,10],[2,3],[4,5]]
intervals.sort(key=lambda x: x[1])
last_end = float('-inf')
count = 0
for s, e in intervals:
    if s >= last_end:
        count += 1; last_end = e
print('Max non-overlapping:', count)  # 2

จุดตัดของรายการช่วง

จุดตัดของรายการช่วง (LeetCode 986): ค้นหาคู่ช่วงที่ตัดกันทั้งหมดจากรายการช่วงสองรายการที่เรียงลำดับแล้ว ให้ใช้วิธี ตัวชี้สองตัว ในแต่ละขั้น ให้คำนวณจุดตัดของคู่ปัจจุบัน (ค่าสูงสุดของเวลาเริ่มต้นและค่าต่ำสุดของเวลาสิ้นสุด) หากเวลาเริ่มต้น ≤ เวลาสิ้นสุด จุดตัดนั้นถือว่าใช้ได้ จากนั้นเลื่อนตัวชี้ของช่วงที่สิ้นสุดก่อน ใช้เวลา O(m+n)

def interval_intersection(A, B):
    result = []
    i = j = 0
    while i < len(A) and j < len(B):
        # Intersection boundaries
        lo = max(A[i][0], B[j][0])
        hi = min(A[i][1], B[j][1])
        if lo <= hi:
            result.append([lo, hi])  # valid intersection
        # Advance pointer of interval that ends first
        if A[i][1] < B[j][1]:
            i += 1
        else:
            j += 1
    return result

A = [[0,2],[5,10],[13,23],[24,25]]
B = [[1,5],[8,12],[15,24],[25,26]]
print(interval_intersection(A, B))
# [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]

การแบ่งส่วนตามอักขระ

การแบ่งส่วนตามอักขระ (LeetCode 763): แบ่งสตริงออกเป็นส่วนต่าง ๆ ให้ได้มากที่สุด โดยอักขระแต่ละตัวต้องปรากฏอยู่ในไม่เกินหนึ่งส่วน วิธีแบบละโมบ: สำหรับอักขระแต่ละตัว ให้หาตำแหน่งสุดท้ายที่อักขระนั้นปรากฏ เดินผ่านสตริงโดยรักษาค่า max_end ไว้ เมื่อ i == max_end ส่วนปัจจุบันจะเสร็จสมบูรณ์ — ให้บันทึกความยาวของส่วนนั้นและเริ่มส่วนใหม่ นี่คือปัญหาการรวมช่วงในรูปแบบที่แฝงอยู่

def partition_labels(s):
    last = {c: i for i, c in enumerate(s)}  # last occurrence of each char
    partitions = []
    start = max_end = 0
    for i, c in enumerate(s):
        max_end = max(max_end, last[c])
        if i == max_end:  # partition complete
            partitions.append(max_end - start + 1)
            start = i + 1
    return partitions

print(partition_labels('ababcbacadefegdehijhklij'))
# [9, 7, 8] — parts 'ababcbaca', 'defegde', 'hijhklij'

สรุปปัญหาเกี่ยวกับช่วง

จงทำความเข้าใจรูปแบบสำคัญสี่แบบสำหรับช่วง: (1) การรวม: sort ตามเวลาเริ่มต้น และขยายช่วงสุดท้ายหากมีการซ้อนทับ (2) การนับห้อง: sort ตามเวลาเริ่มต้น และใช้มินิฮีปของเวลาสิ้นสุด (3) ช่วงที่ไม่ซ้อนทับกันมากที่สุด: sort ตามเวลาสิ้นสุด แล้วเลือกด้วยวิธีแบบละโมบ (4) การแทรก: การไล่ตรวจสามช่วงเชิงเส้น สิ่งสำคัญคือคีย์ที่ใช้ sort: การรวมใช้เวลาเริ่มต้น ส่วนการเลือกจำนวนมากที่สุดใช้เวลาสิ้นสุด ความซับซ้อนด้านเวลาเป็น O(n log n) เสมอ โดยมี sort เป็นส่วนที่ใช้เวลามากที่สุด ส่วนการรวมและการไล่ตรวจใช้เวลา O(n)

# Quick reference:
# Merge intervals:         sort by start, extend if overlap
# Insert interval:         three-phase linear scan
# Meeting rooms (can?):   sort by start, check consecutive overlap
# Meeting rooms (min?):   sort by start, min-heap of end times / sweep
# Max non-overlapping:    sort by END, greedy keep
# Min removals:           n - max_non_overlapping
# Interval intersection:  two pointers on sorted lists

print('Pattern: sort key is the decisive choice')
print('Merge → sort by start')
print('Activity selection → sort by end')
print('Room count → sort by start + heap of ends')

ตรวจสอบอย่างรวดเร็ว

ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูล & อัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า การรวมช่วงทำได้โดยเรียงตามเวลาเริ่มต้นและขยายช่วงสุดท้ายเมื่อเกิดการซ้อนทับ การหาจำนวนห้องประชุมขั้นต่ำใช้การ sort ตามเวลาเริ่มต้นร่วมกับมินิฮีปของเวลาสิ้นสุด โดยนำห้องที่สิ้นสุดเร็วที่สุดกลับมาใช้เมื่อห้องว่าง และ การหาช่วงที่ไม่ซ้อนทับกันมากที่สุดใช้การเลือกแบบละโมบโดยเรียงตามเวลาสิ้นสุด ต่อไปเราจะจัดการกับเกมกระโดด I และ II ซึ่งเป็นปัญหาการเข้าถึงและการหาจำนวนการกระโดดน้อยที่สุดที่แก้ได้ด้วยการขยายช่วงแบบละโมบ

คำถามที่พบบ่อย

บทเรียน “การจัดตารางช่วงเวลาและการรวมช่วง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การจัดตารางช่วงเวลาและการรวมช่วง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การจัดตารางช่วงเวลาและการรวมช่วง”

แก้ปัญหาห้องประชุมและช่วงเวลาที่ไม่ทับซ้อนด้วยการเรียงตามเวลาสิ้นสุด และรวมช่วงเวลาด้วยการเรียงตามเวลาเริ่มต้น คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การจัดตารางช่วงเวลาและการรวมช่วง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม

ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

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

  1. ละโมบกับ DP: ควรใช้แบบใด
  2. การจัดตารางช่วงเวลาและการรวมช่วง
  3. เกมกระโดด I และ II
  4. ตัวจัดตารางงานและสถานีเติมน้ำมัน
← กลับไปที่ Coding Interview Prep