مؤشران: البطيء والسريع
طبّق نمط المؤشرين البطيء والسريع لإزالة التكرارات في مكانها وتحريك الأصفار وتقسيم المصفوفات حول قيمة محورية
مؤشران: البطيء والسريع درس مجاني في DSA Interview Prep على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DSA Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
شرح المؤشرين البطيء والسريع
يستخدم نمط المؤشر البطيء والسريع (ويُسمى أيضًا نمط السلحفاة والأرنب) مؤشرين يتحركان بسرعتين مختلفتين عبر التسلسل نفسه. وعلى خلاف مؤشري الطرفين المتقابلين، يبدأ كلاهما من البداية. يتقدم المؤشر البطيء خطوة واحدة في كل مرة، بينما يتقدم المؤشر السريع خطوتين (أو أكثر). وينشئ اختلاف السرعة بينهما ثوابت مفيدة: يتتبع المؤشر البطيء «بادئة صالحة»، بينما يبحث المؤشر السريع إلى الأمام عن الشروط المطلوبة.
# Slow pointer marks the write position;
# Fast pointer scans for next non-duplicate.
def remove_duplicates(nums):
if not nums: return 0
slow = 0 # next position to write a unique value
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1 # new length
nums = [1, 1, 2, 3, 3, 3, 4]
k = remove_duplicates(nums)
print(nums[:k]) # [1, 2, 3, 4]إزالة التكرارات من مصفوفة مرتبة
في المصفوفة المرتبة، تكون العناصر المكررة متجاورة. يتتبع المؤشر البطيء آخر قيمة فريدة كُتبت، بينما يبحث المؤشر السريع إلى الأمام. عندما يصل المؤشر السريع إلى قيمة مختلفة عن nums[slow]، حرّك slow وانسخ القيمة الجديدة. تعمل هذه الخوارزمية داخل المصفوفة نفسها بزمن O(n) ومساحة إضافية O(1) — وهي مسألة شائعة في المقابلات تختبر إتقان نمط مؤشر القراءة والكتابة.
def remove_duplicates_v2(nums):
slow = 0
for fast in range(len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
# Allow at most 2 occurrences
def remove_duplicates_k2(nums):
slow = 0
for fast in range(len(nums)):
if slow < 2 or nums[fast] != nums[slow - 2]:
nums[slow] = nums[fast]
slow += 1
return slow
print(remove_duplicates_k2([1,1,1,2,2,3]))
# Result: 5, nums[:5] = [1,1,2,2,3]نقل الأصفار باستخدام المؤشرين البطيء والسريع
انقل جميع الأصفار إلى النهاية مع الحفاظ على الترتيب النسبي للعناصر غير الصفرية. يحدد المؤشر البطيء الموضع التالي لعنصر غير صفري. يبحث المؤشر السريع عن القيم غير الصفرية. عندما يعثر fast على قيمة، انسخها إلى موضع slow وحرّك المؤشرين معًا. بعد انتهاء البحث، املأ المواضع من slow حتى النهاية بالأصفار. الزمن O(n)، والمساحة O(1).
def move_zeroes(nums):
slow = 0 # next position for a non-zero
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow] = nums[fast]
slow += 1
# Fill rest with zeroes
while slow < len(nums):
nums[slow] = 0
slow += 1
nums = [0, 1, 0, 3, 12]
move_zeroes(nums)
print(nums) # [1, 3, 12, 0, 0]تقسيم المصفوفة حول محور
تعيد خطوة التقسيم الفرعية في الفرز السريع ترتيب العناصر داخل المصفوفة نفسها بحيث تسبق جميع القيم < pivot القيم >= pivot. يستخدم مخطط Lomuto مؤشرًا بطيئًا (يحدد آخر موضع لعنصر صغير) ومؤشرًا سريعًا (يبحث إلى الأمام). عندما يعثر fast على عنصر صغير، زِد slow ثم بدّل العنصرين. تعمل هذه الخطوة بزمن O(n) ومساحة إضافية O(1).
def lomuto_partition(nums, low, high):
pivot = nums[high]
slow = low - 1 # last position of small element
for fast in range(low, high):
if nums[fast] <= pivot:
slow += 1
nums[slow], nums[fast] = nums[fast], nums[slow]
# Place pivot in final position
nums[slow+1], nums[high] = nums[high], nums[slow+1]
return slow + 1 # pivot's final index
arr = [3, 1, 4, 1, 5, 9, 2, 6]
p = lomuto_partition(arr, 0, len(arr)-1)
print(arr) # elements before p are <= pivotالعثور على منتصف قائمة مرتبطة
باستخدام المؤشرين البطيء والسريع في قائمة مرتبطة، يتقدم المؤشر السريع عبر عقدتين في كل خطوة، بينما يتقدم المؤشر البطيء عبر عقدة واحدة. عندما يصل fast إلى النهاية، يكون slow عند المنتصف. هذا الأسلوب ذو المرور الواحد وبزمن O(n) أبسط بكثير من عدّ العقد ثم السير حتى المنتصف. ويُستخدم كخطوة فرعية في الفرز بالدمج للقوائم المرتبطة وفي اكتشاف القوائم المرتبطة المتناظرة.
class Node:
def __init__(self, val, nxt=None):
self.val = val
self.next = nxt
def find_middle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow # slow is at middle
# Build 1->2->3->4->5
h = Node(1, Node(2, Node(3, Node(4, Node(5)))))
mid = find_middle(h)
print(mid.val) # 3 (middle of 5 nodes)اكتشاف الدورة: سلحفاة وأرنب Floyd
تضع خوارزمية اكتشاف الدورات لدى Floyd المؤشرين البطيء والسريع عند رأس قائمة مرتبطة. يتقدم المؤشر البطيء عبر عقدة واحدة، بينما يتقدم السريع عبر عقدتين. إذا وُجدت دورة، فسوف يلحق المؤشر السريع بالمؤشر البطيء ويدور حوله في النهاية، وسيلتقيان داخل الدورة. إذا وصل fast إلى None، فلا توجد دورة. ويكون اللقاء مضمونًا لأن fast يكتسب خطوة واحدة على slow في كل تكرار — ففي دورة طولها k، يلتقيان خلال k خطوات من دخول slow إلى الدورة.
class ListNode:
def __init__(self, val=0, nxt=None):
self.val = val
self.next = nxt
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast: # identity check (same object)
return True
return False
# 1->2->3->4->2 (cycle at node 2)
n1 = ListNode(1)
n2 = ListNode(2)
n3 = ListNode(3)
n4 = ListNode(4)
n1.next=n2; n2.next=n3; n3.next=n4; n4.next=n2
print(has_cycle(n1)) # Trueالعثور على نقطة دخول الدورة
بعد اكتشاف دورة (slow == fast)، أعد أحد المؤشرين إلى الرأس. ثم حرّك كلا المؤشرين خطوة واحدة في كل مرة. سيلتقيان عند نقطة دخول الدورة. يعتمد ذلك على الخاصية الرياضية التي تنص على أن المسافة من الرأس إلى نقطة دخول الدورة تساوي المسافة من نقطة اللقاء إلى نقطة دخول الدورة (بحساب طول الدورة). وهذه نتيجة رياضية جميلة تظهر كثيرًا في مسائل المقابلات الصعبة.
def detect_cycle(head):
slow = fast = head
# Phase 1: detect
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
break
else:
return None # no cycle
# Phase 2: find entry
slow = head
while slow is not fast:
slow = slow.next
fast = fast.next
return slow # cycle entry node
# Using same cycled list as previous scene
print(detect_cycle(n1).val) # 2 (cycle entry)استخدام المؤشرين البطيء والسريع مع العدد السعيد
ينطبق المؤشران البطيء والسريع خارج القوائم المرتبطة أيضًا، على أي عملية تتضمن دورة. يمر «العدد السعيد» عبر دورات من مجاميع مربعات الأرقام — فإذا لم يكن n سعيدًا، تدخل السلسلة في حلقة في النهاية. اكتشف الحلقة باستخدام slow (خطوة واحدة = مربع رقم واحد) وfast (خطوتان). إذا التقيا عند 1، فإن n سعيد؛ وإلا فهو عالق في دورة لا تتضمن 1. هذه خوارزمية Floyd مطبقة على قائمة مرتبطة افتراضية من القيم.
def is_happy(n):
def next_val(x):
total = 0
while x:
x, d = divmod(x, 10)
total += d * d
return total
slow = n
fast = next_val(n)
while fast != 1 and slow != fast:
slow = next_val(slow)
fast = next_val(next_val(fast))
return fast == 1
print(is_happy(19)) # True (1->9->...->1)
print(is_happy(2)) # False (enters a cycle)العقدة رقم n من نهاية القائمة
اعثر على العقدة رقم n من نهاية قائمة مرتبطة في مرور واحد باستخدام مؤشرين. حرّك المؤشر السريع n خطوات إلى الأمام. ثم حرّك المؤشرين معًا حتى يصل fast إلى النهاية — عندها يكون slow عند العقدة رقم n من النهاية. لحذف هذه العقدة، احتفظ بمؤشر 'prev' متأخرًا عن slow بخطوة واحدة. هذه مسألة كلاسيكية في القوائم المرتبطة ذات المرور الواحد، وتتجنب عدّ الطول الإجمالي أولًا.
def remove_nth_from_end(head, n):
dummy = ListNode(0)
dummy.next = head
fast = slow = dummy
# Advance fast n+1 steps
for _ in range(n + 1):
fast = fast.next
# Advance together
while fast:
slow = slow.next
fast = fast.next
# slow.next is the nth from end
slow.next = slow.next.next
return dummy.next
# Build 1->2->3->4->5, remove 2nd from end
h2 = ListNode(1,ListNode(2,ListNode(3,ListNode(4,ListNode(5)))))
result = remove_nth_from_end(h2, 2)
# Should give 1->2->3->5المؤشران البطيء والسريع في مسائل السلاسل النصية
ينطبق التفكير بالمؤشرين البطيء والسريع على مسائل المصفوفات والسلاسل النصية أيضًا. عند ضغط سلسلة نصية مرمّزة بترميز طول التتابع، يحدد المؤشر البطيء موضع الكتابة، بينما يبحث المؤشر السريع حتى نهاية كل تتابع. عندما تساوي جميع الأحرف في التتابع حرف slow، حرّك fast؛ وإلا فسجّل التتابع وحدّث slow. يحقق هذا الأسلوب O(n) في مرور واحد وبمساحة O(1).
def compress(chars):
slow = fast = 0
while fast < len(chars):
char = chars[fast]
count = 0
# Count the run
while fast < len(chars) and chars[fast] == char:
fast += 1
count += 1
chars[slow] = char
slow += 1
if count > 1:
for c in str(count):
chars[slow] = c
slow += 1
return slow
chars = list('aabcccccaa')
print(compress(chars)) # 6
print(chars[:6]) # ['a','2','b','c','5','a']... wait
# Actually: ['a','2','b','c','5','a','2']الاختيار بين المؤشرين البطيء والسريع ومؤشري الطرفين المتقابلين
استخدم مؤشري الطرفين المتقابلين عندما تتضمن المسألة أزواجًا مجموعها يساوي هدفًا، أو التحقق من التناظر، أو تضييق نافذة من الجانبين. واستخدم المؤشرين البطيء والسريع عندما تحتاج إلى مؤشر كتابة (لإزالة العناصر أو نقلها)، أو عند معالجة بنية قائمة مرتبطة (العثور على المنتصف أو الدورة)، أو عند اكتشاف دورات في أي تسلسل من القيم. يلغي كلا الأسلوبين الحلقات المتداخلة ويحققان O(n) — والعامل الحاسم هو بنية المرور.
# Pattern matcher:
# 1. Sorted array, target sum -> OPPOSITE ENDS
# 2. Remove/filter elements in-place -> SLOW-FAST (read-write)
# 3. Linked list middle/cycle -> SLOW-FAST (1x vs 2x speed)
# 4. Detect cycle in value sequence -> SLOW-FAST (Floyd)
# Example: given sorted array, remove val in-place
def remove_sorted(nums, val):
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
return slow
nums = [0,1,2,2,3,0,4,2]
print(remove_sorted(nums, 2)) # 5تحقق سريع
اختبر مدى فهمك لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.
مراجعة الدرس
في هذا الدرس تعلّمتم أن: نمط المؤشرين البطيء والسريع (القراءة والكتابة) يحافظ على مؤشر كتابة عند الموضع الصالح التالي، بينما يبحث مؤشر سريع إلى الأمام — وهو أساس الإزالة وإزالة التكرارات ونقل الأصفار داخل المصفوفة نفسها، تكتشف خوارزمية السلحفاة والأرنب لدى Floyd الدورات في زمن O(n) ومساحة O(1) باستغلال اختلاف السرعة بين المؤشرين، وبعد اكتشاف دورة، يؤدي إعادة أحد المؤشرين إلى الرأس وتحريك كليهما بالسرعة نفسها إلى العثور على نقطة دخول الدورة، بفضل تساوي مسافتين يمكن إثباته. بعد ذلك سنستكشف واجهة Python البرمجية للسلاسل النصية المخصصة للمقابلات.
الأسئلة الشائعة
هل درس «مؤشران: البطيء والسريع» مجاني؟
نعم — نص درس «مؤشران: البطيء والسريع» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DSA Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «مؤشران: البطيء والسريع»؟
طبّق نمط المؤشرين البطيء والسريع لإزالة التكرارات في مكانها وتحريك الأصفار وتقسيم المصفوفات حول قيمة محورية تتمرن على DSA Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DSA Interview Prep؟
لا تُشترط خبرة سابقة. DSA Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «مؤشران: البطيء والسريع»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DSA Interview Prep هذا؟
نعم. كل درس في DSA Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- أساسيات المصفوفات والعمليات في مكانها
- المجاميع البادئة والإجماليات التراكمية
- مؤشران: الطرفان المتقابلان
- مؤشران: البطيء والسريع