0Pricing
Coding Interview Prep · درس

تنفيذ المكدس وتطبيقاته

نفّذ مكدسًا باستخدام push وpop وpeek، ثم حل valid-parentheses وmin-stack وتقييم تدوين البولندي العكسي

تنفيذ المكدس وتطبيقاته درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

بنية بيانات المكدس

المكدس stack هو بنية بيانات تعمل وفق مبدأ الوارد أخيرًا، الصادر أولًا (LIFO). فالعنصر الأخير الذي يُدفع إلى المكدس هو أول عنصر يُسحب منه. تخيلوا كومة من الأطباق: لا يمكنكم الإضافة أو الإزالة إلا من الأعلى. العمليات الأساسية هي push (الإضافة إلى الأعلى)، وpop (الإزالة من الأعلى)، وpeek (قراءة العنصر العلوي دون إزالته). تستغرق العمليات الثلاث O(1) في المكدس المُنفذ جيدًا.

في Python، تؤدي القائمة دور المكدس على نحو مثالي: إذ تمثل append عملية push، وتمثل pop() عملية pop، وتمثل [-1] عملية peek.

stack = []

# Push
stack.append(10)
stack.append(20)
stack.append(30)
print('After pushes:', stack)  # [10, 20, 30]

# Peek
print('Top:', stack[-1])       # 30

# Pop
print('Popped:', stack.pop())  # 30
print('After pop:', stack)     # [10, 20]

فئة المكدس مع Push وPop وPeek وisEmpty

يمنح تغليف القائمة داخل فئة واجهةً أوضح، ويمنع الاستخدام غير المقصود لعمليات ليست من عمليات المكدس، مثل insert أو الفهرسة في مواضع غير الموضع العلوي. هذا هو التنفيذ الذي يتوقعه المحاورون عند مطالبتكم بـ «تنفيذ مكدس من الصفر».

class Stack:
    def __init__(self):
        self._data = []

    def push(self, val):
        self._data.append(val)

    def pop(self):
        if self.is_empty():
            raise IndexError('pop from empty stack')
        return self._data.pop()

    def peek(self):
        if self.is_empty():
            raise IndexError('peek at empty stack')
        return self._data[-1]

    def is_empty(self):
        return len(self._data) == 0

    def __len__(self):
        return len(self._data)

s = Stack()
s.push(1); s.push(2); s.push(3)
print(s.peek())  # 3
print(s.pop())   # 3
print(len(s))    # 2

الأقواس المتوازنة (LeetCode 20)

LeetCode 20 «الأقواس المتوازنة»: تحديد ما إذا كانت سلسلة من الأقواس متوازنة. لكل قوس فتح، نفّذ push له. ولكل قوس إغلاق، تحقق من أن العنصر العلوي في المكدس هو قوس الفتح المطابق؛ وإذا لم يكن كذلك، أو كان المكدس فارغًا، فأعد False. إذا كان المكدس فارغًا في النهاية، فالسلسلة صالحة. هذا هو أول تطبيق أساسي للمكدس في مقابلات البرمجة.

def isValid(s):
    stack = []
    matching = {')': '(', '}': '{', ']': '['}
    for ch in s:
        if ch in '([{':
            stack.append(ch)
        else:
            if not stack or stack[-1] != matching[ch]:
                return False
            stack.pop()
    return len(stack) == 0

print(isValid('()[]{}'))    # True
print(isValid('([)]'))      # False
print(isValid('{[]}'))      # True
print(isValid(']'))         # False

مكدس الحد الأدنى (LeetCode 155)

LeetCode 155 «مكدس الحد الأدنى»: تصميم مكدس يدعم push وpop وpeek وgetMin جميعها في O(1). تكمن الحيلة في الحفاظ على مكدس ثانٍ يتتبع القيمة الدنيا في كل لحظة. عند تنفيذ push، أضف القيمة أيضًا إلى مكدس الحد الأدنى إذا كانت القيمة الجديدة <= القيمة الدنيا الحالية (أو إذا كان مكدس الحد الأدنى فارغًا). وعند تنفيذ pop، نفّذ pop أيضًا على مكدس الحد الأدنى إذا كانت القيمة المسحوبة تساوي القيمة الدنيا الحالية.

class MinStack:
    def __init__(self):
        self.stack = []
        self.min_stack = []

    def push(self, val):
        self.stack.append(val)
        if not self.min_stack or val <= self.min_stack[-1]:
            self.min_stack.append(val)

    def pop(self):
        val = self.stack.pop()
        if val == self.min_stack[-1]:
            self.min_stack.pop()
        return val

    def top(self):
        return self.stack[-1]

    def getMin(self):
        return self.min_stack[-1]

ms = MinStack()
ms.push(-2); ms.push(0); ms.push(-3)
print(ms.getMin())  # -3
ms.pop()
print(ms.top())     # 0
print(ms.getMin())  # -2

تقييم تدوين بولندي عكسي

LeetCode 150 «تقييم التدوين البولندي العكسي» (التدوين اللاحق): تُدفع المعاملات إلى المكدس؛ وعند مواجهة عامل، اسحب معاملين، وطبّق العامل، ثم ادفع النتيجة. الترتيب مهم عند الطرح والقسمة: فالمعامل المسحوب أولًا هو المعامل الأيمن، والمعامل المسحوب ثانيًا هو المعامل الأيسر.

def evalRPN(tokens):
    stack = []
    ops = set(['+', '-', '*', '/'])
    for tok in tokens:
        if tok not in ops:
            stack.append(int(tok))
        else:
            b = stack.pop()  # right operand
            a = stack.pop()  # left operand
            if tok == '+':
                stack.append(a + b)
            elif tok == '-':
                stack.append(a - b)
            elif tok == '*':
                stack.append(a * b)
            else:             # division truncated toward zero
                stack.append(int(a / b))
    return stack[0]

print(evalRPN(['2','1','+','3','*']))     # 9
print(evalRPN(['4','13','5','/','+']))    # 6
print(evalRPN(['10','6','9','3','+','-11','*','/','*','17','+','5','+']))  # 22

فك ترميز سلسلة نصية (LeetCode 394)

LeetCode 394 «فك ترميز سلسلة نصية»: عند إعطائك سلسلة مشفرة مثل 3[a2[c]]، وسّعها إلى accaccacc. استخدم مكدسين: أحدهما لأعداد التكرار، والآخر للسلاسل المتراكمة. عند مواجهة رقم، كوّن العدد الكامل. عند مواجهة [، ادفع السلسلة الحالية والعدد. عند مواجهة ]، اسحب القيم وكرّر المقطع الحالي. وعند مواجهة حرف، أضفه إلى السلسلة الحالية.

def decodeString(s):
    count_stack = []
    str_stack   = []
    current_str = ''
    current_num = 0
    for ch in s:
        if ch.isdigit():
            current_num = current_num * 10 + int(ch)
        elif ch == '[':
            count_stack.append(current_num)
            str_stack.append(current_str)
            current_str = ''
            current_num = 0
        elif ch == ']':
            repeats = count_stack.pop()
            current_str = str_stack.pop() + current_str * repeats
        else:
            current_str += ch
    return current_str

print(decodeString('3[a]2[bc]'))    # 'aaabcbc'
print(decodeString('3[a2[c]]'))     # 'accaccacc'
print(decodeString('2[abc]3[cd]ef')) # 'abcabccdcdcdef'

درجات الحرارة اليومية (معاينة المكدس الرتيب)

LeetCode 739 «درجات الحرارة اليومية»: لكل يوم، اعثر على عدد الأيام حتى ظهور درجة حرارة أعلى. يستغرق الحل بالقوة الغاشمة O(n²). باستخدام مكدس، مرّر على درجات الحرارة؛ ولكل يوم، اسحب جميع عناصر المكدس (فهارس الأيام) التي تكون درجة حرارتها أقل من درجة حرارة اليوم الحالي. تكون الإجابة لتلك الأيام المسحوبة هي (today - popped_day). ثم ادفع اليوم الحالي. أما العناصر المتبقية في المكدس فلم تجد يومًا أكثر دفئًا، لذا تكون إجابتها 0.

def dailyTemperatures(temps):
    result = [0] * len(temps)
    stack  = []  # stores indices
    for i, t in enumerate(temps):
        while stack and temps[stack[-1]] < t:
            j = stack.pop()
            result[j] = i - j
        stack.append(i)
    return result

print(dailyTemperatures([73,74,75,71,69,72,76,73]))
# [1, 1, 4, 2, 1, 1, 0, 0]

استخدام المكدس لاجتياز DFS

يمكن استبدال مكدس الاستدعاءات في DFS التكراري بمكدس صريح، مما يجعل الخوارزمية تكرارية. ادفع الجذر؛ وما دام المكدس غير فارغ، اسحب عقدة، وعالجها، ثم ادفع أبناءها (الابن الأيمن قبل الأيسر للمعالجة من اليسار إلى اليمين). يتطابق هذا الاجتياز التكراري لـ DFS في سلوكه مع DFS التكراري، لكنه يتجنب حد الاستدعاء التكراري في Python عند التعامل مع الأشجار العميقة.

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val   = val
        self.left  = left
        self.right = right

def preorder_iterative(root):
    if not root:
        return []
    result, stack = [], [root]
    while stack:
        node = stack.pop()
        result.append(node.val)
        if node.right:
            stack.append(node.right)  # push right first
        if node.left:
            stack.append(node.left)   # so left is processed first
    return result

root = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)), TreeNode(3))
print(preorder_iterative(root))  # [1, 2, 4, 5, 3]

تعقيد الزمن والمساحة

تستغرق جميع عمليات المكدس (push وpop وpeek وisEmpty) O(1) بالتكلفة المتوسطة. ويتطلب بناء مكدس من n عنصرًا زمنًا قدره O(n). وتكون المساحة O(n) في أسوأ الحالات عندما تُخزّن جميع العناصر. أما في المسائل التي تستخدم مكدسًا رتيبًا، فيُدفَع كل عنصر ويُسحَب مرة واحدة كحد أقصى، ما يمنح زمنًا إجماليًا قدره O(n) عبر جميع التكرارات، وليس O(n²) كما قد يوحي تحليل الحلقة الخارجية بطريقة ساذجة.

# Demonstrate O(n) total for monotonic stack
# Each element pushed once, popped at most once => 2n operations total

def count_ops(n):
    pushes = pops = 0
    stack = []
    for i in range(n):
        while stack and stack[-1] < i:  # simulated decreasing condition
            stack.pop()
            pops += 1
        stack.append(i)
        pushes += 1
    return pushes, pops

p, pp = count_ops(1000)
print(f'Pushes: {p}, Pops: {pp}, Total ops: {p+pp}')  # <= 2000

أكبر مستطيل في المدرج التكراري (معاينة)

تُعد مسألة LeetCode 84 «أكبر مستطيل في المدرج التكراري» أصعب مسائل المكدس الكلاسيكية. لكل عمود، يمتد المستطيل الذي يمكن أن يرتكز عليه إلى اليسار حتى العثور على عمود أقصر، وإلى اليمين حتى العثور على عمود أقصر. يتتبع مكدس رتيب فهارس الأعمدة بترتيب متزايد للارتفاع. عند رؤية عمود أقصر، اسحب العناصر واحسب المستطيل باستخدام ارتفاع العمود المسحوب. يوفر المكدس حدود اليسار واليمين في O(1) لكل عملية سحب.

def largestRectangleArea(heights):
    stack  = []  # indices, increasing heights
    result = 0
    heights = heights + [0]  # sentinel forces all pops
    for i, h in enumerate(heights):
        while stack and heights[stack[-1]] > h:
            height = heights[stack.pop()]
            width  = i if not stack else i - stack[-1] - 1
            result = max(result, height * width)
        stack.append(i)
    return result

print(largestRectangleArea([2,1,5,6,2,3]))  # 10
print(largestRectangleArea([2,4]))           # 4

استراتيجية المقابلة لمسائل المكدس

غالبًا ما تتخفى مسائل المكدس في صورة «المعالجة من الداخل إلى الخارج» أو «العثور على العنصر الأكبر/الأصغر التالي». ومن الإشارات إلى أن المكدس قد يكون مفيدًا: حاجتكم إلى العنصر الذي شوهد مؤخرًا، أو مطابقة أزواج (مثل الأقواس والعلامات)، أو الرغبة في تحقيق O(n) في مسألة تتطلب في صورتها الساذجة حلقات متداخلة بزمن O(n²). وتحول المكدسات الرتيبة، على وجه الخصوص، مسألة «العثور على أقرب عنصر أكبر/أصغر لكل عنصر» من O(n²) إلى O(n).

في المقابلة، اذكر ثابت المكدس بوضوح: «سأحافظ على مكدس من الفهارس بترتيب تنازلي للارتفاع».

اختبار سريع

اختبر مدى فهمك لمفاهيم Data Structures & Algorithms — Coding Interview Prep التي تناولها هذا الدرس.

مراجعة الدرس

تعلمت في هذا الدرس أن: قوائم Python تنفذ push وpop وpeek في O(1)، مما يجعلها مكدسات مثالية، وأن الأقواس المتوازنة ومكدس الحد الأدنى هما مسألتا المقابلات الأساسيتان في المكدسات، وأن المكدسات الرتيبة تحل مسائل العنصر الأكبر التالي في O(n) عبر دفع كل عنصر وسحبه مرة واحدة كحد أقصى. بعد ذلك سنبني الطوابير باستخدام deque في Python ونحل مسألة أكبر عنصر في النافذة المنزلقة.

الأسئلة الشائعة

هل درس «تنفيذ المكدس وتطبيقاته» مجاني؟

نعم — نص درس «تنفيذ المكدس وتطبيقاته» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

ماذا ستتعلم في «تنفيذ المكدس وتطبيقاته»؟

نفّذ مكدسًا باستخدام push وpop وpeek، ثم حل valid-parentheses وmin-stack وتقييم تدوين البولندي العكسي تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟

لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «تنفيذ المكدس وتطبيقاته»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟

نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. تنفيذ المكدس وتطبيقاته
  2. تنفيذ الطابور وDeque
  3. نمط المكدس الرتيب
  4. المحاكاة المتبادلة للمكدس والطابور
← العودة إلى Coding Interview Prep