عكس قائمة مرتبطة
اعكس قائمة مرتبطة أحادية بصورة تكرارية عبر إعادة توصيل ثلاثة مؤشرات، وبصورة ذاتية مع تتبع كل خطوة في مخطط يشبه الرسم على السبورة
عكس قائمة مرتبطة درس مجاني في DSA Interview Prep على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DSA Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
لماذا يُعد عكس القائمة أساسياً
يُعد عكس قائمة مرتبطة من أكثر أسئلة مقابلات البرمجة شيوعاً. فهو يختبر قدرتك على معالجة المؤشرات بدقة من دون فقدان تتبّع العقد. وتظهر أشكاله المختلفة بوصفها مسائل مستقلة، وكذلك كخطوات فرعية ضمن خوارزميات أكبر مثل اكتشاف القوائم متناظرة، وإعادة ترتيب القائمة، والعكس ضمن مجموعات من k عناصر.
يستخدم الأسلوب التكراري ثلاثة مؤشرات: prev وcurr وnext_node. أما الأسلوب递归ي فيعبّر عن المنطق نفسه باستخدام المرور عبر مكدس الاستدعاءات. يحقق كلا الأسلوبين زمناً قدره O(n)، ومساحة قدرها O(1) في الأسلوب التكراري.
العكس التكراري باستخدام ثلاثة مؤشرات
في كل خطوة من العكس التكراري: احفظ curr.next حتى لا تفقد بقية القائمة، واعكس curr.next ليشير إلى الخلف نحو prev، ثم حرّك prev إلى curr، وحرّك curr إلى next المحفوظ. عندما تصبح قيمة curr هي None، تنتهي الحلقة ويصبح prev هو الرأس الجديد.
وسيلة مفيدة لتذكّر الخطوات: احفظ، اعكس، حرّك، حرّك.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_list(head):
prev, curr = None, head
while curr:
next_node = curr.next # Save
curr.next = prev # Flip
prev = curr # Advance prev
curr = next_node # Advance curr
return prev # new head
# Test
nodes = [ListNode(i) for i in range(1, 6)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverse_list(nodes[0])
while head:
print(head.val, end=' ') # 5 4 3 2 1
head = head.nextالتتبّع خطوة بخطوة
دعونا نتتبّع reverse_list على 1 -> 2 -> 3. في البداية prev=None, curr=1. الخطوة 1: احفظ next=2، واعكس 1.next=None، ثم اجعل prev=1 وcurr=2. الخطوة 2: احفظ next=3، واعكس 2.next=1، ثم اجعل prev=2 وcurr=3. الخطوة 3: احفظ next=None، واعكس 3.next=2، ثم اجعل prev=3 وcurr=None. تنتهي الحلقة؛ أعد prev=3، وهو الرأس الجديد لـ 3 -> 2 -> 1.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_list_traced(head):
prev, curr = None, head
step = 0
while curr:
step += 1
next_node = curr.next
curr.next = prev
print(f'Step {step}: flipped {curr.val}.next -> {prev.val if prev else None}')
prev = curr
curr = next_node
return prev
nodes = [ListNode(i) for i in [1, 2, 3]]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverse_list_traced(nodes[0])
print('New head:', head.val) # 3العكس باستخدام الاستدعاء الذاتي
يعتمد النهج القائم على الاستدعاء الذاتي على أن reverse_list(head.next) يعيد الرأس الجديد للجزء اللاحق الذي عُكست قائمته بالفعل. ولا يتبقى سوى عكس المؤشر بين head وhead.next: اضبط head.next.next = head (وجّه العقدة الثانية القديمة إلى العقدة الأولى القديمة) وhead.next = None (اقطع الرابط الأمامي القديم). يصعد الرأس الجديد تدريجيًا من حالة الأساس.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_list_rec(head):
# Base case: empty or single node
if not head or not head.next:
return head
new_head = reverse_list_rec(head.next) # reverse suffix
head.next.next = head # former second node points back
head.next = None # sever forward link
return new_head
nodes = [ListNode(i) for i in range(1, 5)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverse_list_rec(nodes[0])
while head:
print(head.val, end=' ') # 4 3 2 1
head = head.nextعكس قائمة فرعية (LeetCode 92)
تطلب مسألة LeetCode 92 «عكس القائمة المترابطة II» عكس القائمة الفرعية من الموضع left إلى الموضع right (مع ترقيم يبدأ من 1) في مرور واحد. تكمن الحيلة في تحديد العقدة التي تسبق القائمة الفرعية (استخدم رأسًا وهميًا حتى يكون ذلك صالحًا دائمًا)، ثم تنفيذ عكس المؤشرات الثلاثة لعدد خطوات يساوي (right - left) تمامًا، وأخيرًا إعادة وصل الجزء المعكوس ببقية القائمة.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverseBetween(head, left, right):
dummy = ListNode(0, head)
pre = dummy
# Advance pre to node just before position 'left'
for _ in range(left - 1):
pre = pre.next
curr = pre.next
for _ in range(right - left):
next_node = curr.next
curr.next = next_node.next
next_node.next = pre.next
pre.next = next_node
return dummy.next
nodes = [ListNode(i) for i in range(1, 6)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverseBetween(nodes[0], 2, 4)
while head:
print(head.val, end=' ') # 1 4 3 2 5
head = head.nextعكس العقد في مجموعات من k (LeetCode 25)
تعكس مسألة LeetCode 25 «عكس العقد في مجموعات من k» كل مجموعة متتالية مكوّنة من k عقد. النهج هو: تحقّق من بقاء k عقد؛ وإذا لم تبقَ، فاتركها كما هي. اعكس العقد k التالية باستخدام الطريقة التكرارية، ثم اعكس القائمة المتبقية استدعائيًا ووصلها. يظل التعقيد الزمني O(n)، مع عمق استدعاءات تكرارية يساوي O(n/k).
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverseKGroup(head, k):
# Check if k nodes are available
curr, count = head, 0
while curr and count < k:
curr = curr.next
count += 1
if count < k:
return head # fewer than k nodes left, keep as-is
# Reverse k nodes
prev, curr = None, head
for _ in range(k):
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# head is now the tail of the reversed group
head.next = reverseKGroup(curr, k)
return prev
nodes = [ListNode(i) for i in range(1, 6)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverseKGroup(nodes[0], 2)
while head:
print(head.val, end=' ') # 2 1 4 3 5
head = head.nextقائمة مترابطة متناظرة
تطلب مسألة LeetCode 234 «القائمة المترابطة المتناظرة» التحقق مما إذا كانت القائمة المترابطة متناظرة في زمن O(n) ومساحة O(1). الاستراتيجية هي: حدّد نقطة المنتصف باستخدام مؤشري slow وfast، واعكس النصف الثاني في مكانه، ثم قارن نصفي القائمة عقدةً بعقدة، وبعد ذلك أعد القائمة إلى حالتها الأصلية اختياريًا. تجمع هذه الطريقة بين تحديد نقطة المنتصف والعكس، وهما مهارتان أساسيتان.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def isPalindrome(head):
# Find mid
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
prev, curr = None, slow
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# Compare
left, right = head, prev
while right:
if left.val != right.val:
return False
left = left.next
right = right.next
return True
def build(arr):
d = ListNode(0)
c = d
for v in arr:
c.next = ListNode(v)
c = c.next
return d.next
print(isPalindrome(build([1,2,2,1]))) # True
print(isPalindrome(build([1,2,3]))) # Falseمقارنة بين العكس التكراري والاستدعاء الذاتي
يستخدم العكس التكراري مساحة O(1)، ولذلك يُفضَّل عمومًا. أما العكس باستخدام الاستدعاء الذاتي فيستخدم مساحة مكدس O(n) بسبب عمق الاستدعاءات، وقد يؤدي ذلك إلى تجاوز سعة المكدس عند التعامل مع قوائم طويلة جدًا (فالحد الافتراضي في Python يقارب 1000 مستوى من الاستدعاء الذاتي).
في مقابلة تقنية، نفّذ النسخة التكرارية أولًا لإظهار وعيك بقيود المساحة، ثم اذكر النسخة القائمة على الاستدعاء الذاتي كبديل أوضح إذا كان طول القائمة محدودًا.
import sys
print('Default recursion limit:', sys.getrecursionlimit())
# For a list of 10,000 nodes the recursive reversal would hit this limit
# Iterative reversal has no such constraint
# Increase if needed (use sparingly):
# sys.setrecursionlimit(20000)الأخطاء الشائعة عند عكس القائمة
تتسبب ثلاثة أخطاء في معظم مشكلات العكس تقريبًا. أولًا، عدم حفظ next قبل الكتابة فوقه: تؤدي العبارة curr.next = prev إلى تدمير المرجع الأمامي إذا لم يتم حفظ next_node. ثانيًا، عدم إعادة prev: عند نهاية الحلقة تكون قيمة curr هي None، بينما يكون prev هو الرأس الجديد. ثالثًا، استخدام حالة أساس خاطئة في الاستدعاء الذاتي: يؤدي نسيان not head.next إلى عدم معالجة قائمة مكوّنة من عقدة واحدة، ويتسبب في حدوث AttributeError.
# Minimal correct iterative reversal — annotated against common bugs
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_list(head):
prev, curr = None, head
while curr:
next_node = curr.next # BUG if omitted: lose rest of list
curr.next = prev
prev = curr
curr = next_node
return prev # BUG if you return curr: it is None
nodes = [ListNode(i) for i in [1, 2, 3]]
nodes[0].next = nodes[1]
nodes[1].next = nodes[2]
h = reverse_list(nodes[0])
while h:
print(h.val, end=' ') # 3 2 1
h = h.nextإعادة ترتيب القائمة (LeetCode 143)
تعيد مسألة LeetCode 143 «إعادة ترتيب القائمة» ترتيب L0 → L1 → L2 → ... → Ln إلى L0 → Ln → L1 → Ln-1 → L2 → Ln-2 في زمن O(n) ومساحة O(1). يتكوّن الحل من ثلاث خطوات: تحديد نقطة المنتصف، وعكس النصف الثاني، ثم تشبيك النصفين بالتناوب. إن إتقان العكس يجعل هذه المسألة التي تبدو معقدة مجرد تركيب مباشر لأدوات مألوفة.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reorderList(head):
if not head or not head.next:
return
# Find mid
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
prev, curr = None, slow.next
slow.next = None
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# Interleave
first, second = head, prev
while second:
tmp1, tmp2 = first.next, second.next
first.next = second
second.next = tmp1
first, second = tmp1, tmp2
nodes = [ListNode(i) for i in range(1, 5)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
reorderList(nodes[0])
h = nodes[0]
while h:
print(h.val, end=' ') # 1 4 2 3
h = h.nextالملخّص: العكس لبنة أساسية
نادرًا ما يكون عكس القائمة المترابطة هو الهدف النهائي؛ فهو لبنة أساسية. يعتمد اكتشاف التناظر، والعكس في مجموعات من k، وإعادة ترتيب القائمة، والعكس بين موضعين، كلها على نمط العكس التكراري نفسه باستخدام ثلاثة مؤشرات. وبمجرد أن يصبح هذا النمط تلقائيًا، يمكنكم توجيه تركيزكم الذهني إلى بنية المسألة على المستوى الأعلى.
احرصوا دائمًا على التدرّب على العكس حتى تتمكنوا من كتابته من الذاكرة في أقل من دقيقتين؛ إذ سيظهر بشكل أو بآخر في معظم جولات المقابلات المتعلقة بالقوائم المترابطة.
اختبار سريع
اختبروا فهمكم لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.
مراجعة الدرس
في هذا الدرس تعلّمتم أن: نمط الحفظ-القلب-التقدّم-التقدّم التكراري يعكس القائمة في زمن O(n) ومساحة O(1)، والنهج القائم على الاستدعاء الذاتي يفترض أن الجزء اللاحق معكوس بالفعل ولا يصلح سوى الرابط الأخير، والعكس خطوة أساسية في اكتشاف التناظر، وإعادة ترتيب القائمة، والعكس في مجموعات من k. في الدرس التالي سنستكشف اكتشاف الدورات باستخدام خوارزمية Floyd.
الأسئلة الشائعة
هل درس «عكس قائمة مرتبطة» مجاني؟
نعم — نص درس «عكس قائمة مرتبطة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DSA Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «عكس قائمة مرتبطة»؟
اعكس قائمة مرتبطة أحادية بصورة تكرارية عبر إعادة توصيل ثلاثة مؤشرات، وبصورة ذاتية مع تتبع كل خطوة في مخطط يشبه الرسم على السبورة تتمرن على DSA Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DSA Interview Prep؟
لا تُشترط خبرة سابقة. DSA Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «عكس قائمة مرتبطة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DSA Interview Prep هذا؟
نعم. كل درس في DSA Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- فئة Node وإنشاء القوائم
- عكس قائمة مرتبطة
- اكتشاف الدورات باستخدام خوارزمية Floyd
- الدمج والتقسيم والعثور على العنصر N من النهاية