جدولة الفواصل ودمجها
حلّ مسألتي meeting-rooms وnon-overlapping-intervals بالترتيب حسب وقت الانتهاء، وادمج الفواصل بالترتيب حسب وقت البدء.
جدولة الفواصل ودمجها درس مجاني في DSA Interview Prep على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DSA Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
نظرة عامة على مسائل الفواصل
تظهر مسائل الفواصل باستمرار في مقابلات الجدولة وإدارة التقويم وتخصيص الموارد. وتتمثل الأنماط الأساسية في: دمج الفواصل المتداخلة، وحساب الحد الأدنى من غرف الاجتماعات، والعثور على أكبر مجموعة من الفواصل غير المتداخلة، وإدراج فاصل جديد. تبدأ معظم مسائل الفواصل بالخطوة نفسها: ترتيب الفواصل حسب وقت البدء، أو حسب وقت الانتهاء وفقًا للمسألة. وغالبًا ما يكون تحديد مفتاح الفرز الصحيح هو الجزء الأصعب.
# 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): لديك قائمة من الفواصل، وعليك دمج جميع الفواصل المتداخلة. الخوارزمية: رتّب الفواصل حسب وقت البدء. مرّ على القائمة المرتبة؛ فإذا كان الفاصل الحالي يتداخل مع آخر فاصل مدموج، أي إن بدايته ≤ نهاية آخر فاصل مدموج، فمدّد نهاية آخر فاصل مدموج إلى القيمة العظمى للنهايتين. وإلا فأضف الفاصل الحالي بوصفه فاصلًا مدموجًا جديدًا. الزمن: 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) أضف جميع الفواصل التي تنتهي قبل بدء الفاصل الجديد. (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): أوجد الحد الأدنى من غرف المؤتمرات اللازمة لعقد جميع الاجتماعات في الوقت نفسه. استخدم كومة دنيا لتتبع الغرفة التي ينتهي اجتماعها أولًا. رتّب الاجتماعات حسب وقت البدء. مع كل اجتماع جديد: إذا بدأ بعد وقت انتهاء الاجتماع في الغرفة التي تنتهي أولًا، فأعد استخدام تلك الغرفة، وذلك بإزالتها ثم إضافتها. وإلا فافتح غرفة جديدة. يساوي حجم الكومة في النهاية عدد الغرف المطلوبة.
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) ونهايته (-1). رتّب جميع الأحداث حسب الوقت، وعند التعادل اجعل النهاية قبل البداية إذا أردت اعتبار الفواصل غير شاملة. امسح من اليسار إلى اليمين مع الحفاظ على عدد جارٍ للاجتماعات النشطة. ويكون أكبر عدد هو الحد الأدنى من الغرف المطلوبة. وهذا الأسلوب أكثر وضوحًا لبعض المتعلمين، كما يمكن تعميمه على مسائل عدّ أخرى تتعلق بالفواصل.
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): أوجد الحد الأدنى لعدد الفواصل التي يجب إزالتها كي تصبح الفواصل المتبقية غير متداخلة. وهذا يكافئ العثور على أكبر عدد من الفواصل غير المتداخلة، أو اختيار الأنشطة، ثم إعادة عدد الفواصل المتبقية بوصفه عدد الإزالات. رتّب الفواصل حسب وقت الانتهاء، واحتفظ جشعًا بالفاصل الذي ينتهي أولًا، لأنه يترك أكبر مساحة للفواصل اللاحقة. وعندما يتداخل الفاصل التالي، تجاهله واحسب عملية إزالة.
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) # 2Interval List Intersections
Interval List Intersections (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]]Partition Labels
Partition Labels (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) الدمج: افرز حسب وقت البدء، ومدّد الفترة الأخيرة عند حدوث تداخل. (2) حساب الغرف: افرز حسب وقت البدء، واستخدم كومة دنيا لأوقات الانتهاء. (3) أكبر عدد من الفترات غير المتداخلة: افرز حسب وقت الانتهاء، ثم اختر جشعًا. (4) الإدراج: مسح خطي من ثلاث مراحل. مفتاح الفرز مهم: يستخدم الدمج وقت البدء، بينما يستخدم الاختيار الأقصى وقت الانتهاء. التعقيد الزمني دائمًا O(n log n)، ويهيمن عليه الفرز؛ أما الدمج والمسح فتعقيدهما 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')اختبار سريع
اختبر مدى فهمك لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.
مراجعة الدرس
في هذا الدرس تعلمت: دمج الفترات الزمنية بفرزها حسب وقت البدء ومدّ الفترة الأخيرة عند حدوث تداخل، وحساب الحد الأدنى من غرف الاجتماعات باستخدام الفرز حسب وقت البدء مع كومة دنيا لأوقات الانتهاء، وإعادة استخدام الغرف عندما تصبح الغرفة ذات وقت الانتهاء الأبكر متاحة، والعثور على أكبر عدد من الفترات غير المتداخلة باستخدام الاختيار الجشع بعد الفرز حسب وقت الانتهاء. بعد ذلك سنتناول Jump Game I وJump Game II — وهما مسألتان تتعلقان بإمكانية الوصول والحد الأدنى من القفزات، وتُحلان بتوسيع النطاق جشعًا.
الأسئلة الشائعة
هل درس «جدولة الفواصل ودمجها» مجاني؟
نعم — نص درس «جدولة الفواصل ودمجها» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DSA Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «جدولة الفواصل ودمجها»؟
حلّ مسألتي meeting-rooms وnon-overlapping-intervals بالترتيب حسب وقت الانتهاء، وادمج الفواصل بالترتيب حسب وقت البدء. تتمرن على DSA Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DSA Interview Prep؟
لا تُشترط خبرة سابقة. DSA Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «جدولة الفواصل ودمجها»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DSA Interview Prep هذا؟
نعم. كل درس في DSA Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- الخوارزميات الجشعة مقابل البرمجة الديناميكية: متى تستخدم كلًّا منهما
- جدولة الفواصل ودمجها
- لعبة القفز I وII
- جدولة المهام ومحطة الوقود