การจัดตารางช่วงเวลาและการรวมช่วง
แก้ปัญหาห้องประชุมและช่วงเวลาที่ไม่ทับซ้อนด้วยการเรียงตามเวลาสิ้นสุด และรวมช่วงเวลาด้วยการเรียงตามเวลาเริ่มต้น
การจัดตารางช่วงเวลาและการรวมช่วง เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ละโมบกับ DP: ควรใช้แบบใด
- การจัดตารางช่วงเวลาและการรวมช่วง
- เกมกระโดด I และ II
- ตัวจัดตารางงานและสถานีเติมน้ำมัน