أهم الأنماط البرمجية لحل مشاكل المقابلات التقنية بكفاءة

Salman IyadSalman Iyad
10 min read

لما نحكي عن البرمجة التنافسية أو المقابلات التقنية، بنلاحظ إنه المشكلة مش بعدد المسائل اللي بنحلها، بل في الأنماط البرمجية اللي بنتعلمها ونتقن استخدامها. تعلم الخوارزميات وأنماط البرمجة الفعّالة مش بس بيمكنك من حل مجموعة متنوعة من المشاكل بسرعة وكفاءة، كمان بيساعدك تفهم المشكلة بشكل أعمق وتختار الحل المناسب بطريقة أسرع.

في هالمقال رح أقدم 15 نمط برمجي أساسي تعلمتهم خلال رحلتي على LeetCode. هاي الأنماط ساعدتني بشكل كبير إني أقلل الوقت اللازم لحل المشاكل وسهلت عليّ الأمور بشكل كبير. رح تتعلم من خلال هاي الأنماط إمتى تستخدم كل واحد، وحتلاقي مثال عملي على كل نمط عشان تقدر تطبقه مباشرة. كمان، ضفت روابط لبعض المشاكل على LeetCode اللي ممكن تتدرب عليها وتحسن مهاراتك.

تعلم الأنماط مش بس بيعطيك أفضلية في حل المشاكل، كمان بيزيد ثقتك أثناء المقابلات التقنية وبساعدك تتنقل بين المشاكل بطريقة سلسة ومنطقية. يلا نبلش نستعرض هاي الأنماط وكيف ممكن نطبقها في مواقف مختلفة.


1. Prefix Sum (مجموع القيم السابقة)

نمط Prefix Sum بيستخدم عشان نعمل معالجة مسبقة للمصفوفة، نخلق مصفوفة جديدة بحيث كل عنصر فيها بيعبر عن مجموع العناصر من البداية وحتى الفهرس المعين. هالشي بيساعد كتير في استعلامات مجموع الشرائح بشكل سريع.

أمثلة شائعة:

  • استعلامات متعددة لمجموع على أجزاء معينة من المصفوفة.

الفكرة: نستخدم مصفوفة تحتوي على مجموعات جزئية عشان نحسب مجموع الشرائح بشكل سريع وفعّال.

مثال كود بايثون:

def prefix_sum(nums):
    prefix = [0] * len(nums)
    prefix[0] = nums[0]
    for i in range(1, len(nums)):
        prefix[i] = prefix[i-1] + nums[i]
    return prefix

def range_sum(nums, i, j):
    prefix = prefix_sum(nums)
    return prefix[j] - prefix[i-1] if i > 0 else prefix[j]

أسئلة LeetCode المرتبطة:

  1. Range Sum Query - Immutable

  2. Contiguous Array

  3. Subarray Sum Equals K


2. Two Pointers Pattern (نمط المؤشرين)

نمط Two Pointers كتير مفيد في حالة القوائم أو المصفوفات المرتبة، بنستخدم مؤشرين عشان نلاقي أزواج أو عناصر بتحقق شرط معين. المؤشرين بيمشوا باتجاهات متعاكسة ليوصلوا للحل بشكل أسرع.

أمثلة شائعة:

  • إيجاد زوج من الأرقام في مصفوفة مرتبة يكون مجموعهم يساوي قيمة معينة.

الفكرة: بنستخدم مؤشرين: واحد يبدأ من بداية المصفوفة والثاني من النهاية، ونحركهم حسب الحالة المطلوبة.

مثال كود بايثون:

def two_sum(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        current_sum = nums[left] + nums[right]
        if current_sum == target:
            return [left, right]
        elif current_sum < target:
            left += 1
        else:
            right -= 1
    return []

أسئلة LeetCode المرتبطة:

  1. Two Sum II - Input Array is Sorted

  2. 3Sum

  3. Container With Most Water


3. Sliding Window Pattern (نمط النافذة المنزلقة)

نمط Sliding Window بيساعد في حل مشاكل متعلقة بشرائح متتابعة في المصفوفات أو السلاسل النصية. بنستخدم "نافذة" بحجم ثابت أو متغير تتحرك عبر المصفوفة.

أمثلة شائعة:

  • إيجاد أكبر مجموع لمجموعة من العناصر المتتابعة.

الفكرة: الحفاظ على نافذة بتحافظ على قيم معينة وبتتحرك على طول المصفوفة، وبتحدث النتيجة كل مرة.

مثال كود بايثون:

def max_sum_subarray(nums, k):
    max_sum = current_sum = sum(nums[:k])
    for i in range(k, len(nums)):
        current_sum += nums[i] - nums[i - k]
        max_sum = max(max_sum, current_sum)
    return max_sum

أسئلة LeetCode المرتبطة:

  1. Maximum Average Subarray I

  2. Longest Substring Without Repeating Characters

  3. Minimum Window Substring


4. Fast & Slow Pointers (نمط المؤشرات السريعة والبطيئة)

بنستخدم مؤشرين واحد بيمشي بسرعة والثاني بطيء، عشان نكتشف مشاكل زي الحلقات في القوائم المترابطة، أو لما بدنا نحدد منتصف القائمة.

أمثلة شائعة:

  • اكتشاف الحلقات في القوائم المترابطة.

الفكرة: بنستخدم المؤشرين عشان نتأكد من وجود دورة أو نحدد جزء معين من القائمة.

مثال كود بايثون:

def has_cycle(head):
    slow, fast = head, head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True
    return False

أسئلة LeetCode المرتبطة:

  1. Linked List Cycle

  2. Happy Number

  3. Find the Duplicate Number


5. LinkedList In-place Reversal (نمط عكس القائمة المترابطة في مكانها)

بنستخدم هالنمط عشان نعكس أجزاء معينة من القائمة المترابطة بدون ما نستخدم مساحة إضافية. كل اللي نعمله هو تعديل المؤشرات لعكس العقد.

أمثلة شائعة:

  • عكس جزء معين من القائمة المترابطة.

الفكرة: نعدل المؤشرات لعكس جزء معين من القائمة بدون استخدام مساحة إضافية.

مثال كود بايثون:

def reverse_linked_list(head):
    prev, curr = None, head
    while curr:
        next_temp = curr.next
        curr.next = prev
        prev = curr
        curr = next_temp
    return prev

أسئلة LeetCode المرتبطة:

  1. Reverse Linked List

  2. Reverse Linked List II

  3. Swap Nodes in Pairs


6. Monotonic Stack (نمط المكدس الرتيب)

نمط Monotonic Stack بنستخدمه للحفاظ على ترتيب العناصر (تصاعدي أو تنازلي) في المكدس، وبيساعدنا في حل مشاكل زي إيجاد العنصر الأكبر أو الأصغر التالي.

أمثلة شائعة:

  • إيجاد العنصر الأكبر التالي في المصفوفة.

الفكرة: بنستخدم مكدس لحفظ تسلسل العناصر ونعالج اللي لسه ما وجدنا إله العنصر الأكبر أو الأصغر.

مثال كود بايثون:

def next_greater_element(nums):
    stack, result = [], [-1] * len(nums)
    for i in range(len(nums)):
        while stack and nums[stack[-1]] < nums[i]:
            result[stack.pop()] = nums[i]
        stack.append(i)
    return result

أسئلة LeetCode المرتبطة:

  1. Next Greater Element I

  2. Daily Temperatures

  3. Largest Rectangle in Histogram


7. Top ‘K’ Elements (نمط أعلى K عنصر)

نمط Top K Elements بنستخدمه لإيجاد أكبر أو أصغر **

K** عنصر في مجموعة باستخدام الأكوام أو الفرز. غالبًا بنستخدم كومة ذات أولوية للحفاظ على K عنصر بس في كل مرة.

أمثلة شائعة:

  • إيجاد ثاني أكبر عنصر في مصفوفة غير مرتبة.

الفكرة: بنستخدم كومة ذات أولوية أو فرز للحفاظ على أكبر أو أصغر K عنصر في كل مرة.

مثال كود بايثون:

import heapq

def kth_largest(nums, k):
    heap = []
    for num in nums:
        heapq.heappush(heap, num)
        if len(heap) > k:
            heapq.heappop(heap)
    return heapq.heappop(heap)

أسئلة LeetCode المرتبطة:

  1. Kth Largest Element in an Array

  2. Top K Frequent Elements

  3. Find K Pairs with Smallest Sums


8. Overlapping Intervals (نمط الفترات المتداخلة)

هذا النمط بنستخدمه لحل المشاكل اللي بتتعامل مع فترات متداخلة زي دمج الفترات أو تحديد الفترات اللي بتتداخل مع بعض.

أمثلة شائعة:

  • دمج الفترات الزمنية المتداخلة.

الفكرة: بنرتب الفترات الزمنية أو النطاقات، وبعدين بنبلش ندمج الفترات اللي بتتداخل مع بعضها.

مثال كود بايثون:

def merge_intervals(intervals):
    intervals.sort(key=lambda x: x[0])
    merged = []
    for interval in intervals:
        if not merged or merged[-1][1] < interval[0]:
            merged.append(interval)
        else:
            merged[-1][1] = max(merged[-1][1], interval[1])
    return merged

أسئلة LeetCode المرتبطة:

  1. Merge Intervals

  2. Insert Interval

  3. Non-Overlapping Intervals


9. Modified Binary Search (نمط البحث الثنائي المعدل)

نمط Modified Binary Search بنعدله عشان نقدر نحل مشاكل أكتر تعقيدًا زي البحث في مصفوفات مرتبة أو مصفوفات دائرية.

أمثلة شائعة:

  • البحث عن عنصر في مصفوفة مرتبة ومُدَوّرة.

الفكرة: بنستخدم البحث الثنائي المعدل عشان نبحث في أجزاء مرتبة من المصفوفة.

مثال كود بايثون:

def search_in_rotated_array(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        if nums[left] <= nums[mid]:
            if nums[left] <= target < nums[mid]:
                right = mid - 1
            else:
                left = mid + 1
        else:
            if nums[mid] < target <= nums[right]:
                left = mid + 1
            else:
                right = mid - 1
    return -1

أسئلة LeetCode المرتبطة:

  1. Search in Rotated Sorted Array

  2. Find Minimum in Rotated Sorted Array

  3. Search a 2D Matrix II


10. Binary Tree Traversal (التنقل في الأشجار الثنائية)

نمط Binary Tree Traversal بيشمل التنقل في جميع العقد في شجرة ثنائية حسب أحد الترتيبات المحددة (PreOrder, InOrder, PostOrder).

أمثلة شائعة:

  • التنقل في شجرة ثنائية باستخدام ترتيب InOrder أو PreOrder.

الفكرة: بنستخدم الاستدعاء الذاتي أو المكدسات للتنقل في الشجرة الثنائية والوصول لكل عقدة حسب الترتيب المطلوب.

مثال كود بايثون:

def inorder_traversal(root):
    result, stack = [], []
    while stack or root:
        while root:
            stack.append(root)
            root = root.left
        root = stack.pop()
        result.append(root.val)
        root = root.right
    return result

أسئلة LeetCode المرتبطة:

  1. Binary Tree Inorder Traversal

  2. Binary Tree Level Order Traversal

  3. Binary Tree Maximum Path Sum


11. Depth-First Search (DFS) (البحث العميق أولًا)

نمط DFS بنستخدمه لاستكشاف كل الفروع أو المسارات في الرسوم البيانية أو الأشجار عن طريق التوغل في العمق أولاً قبل الرجوع للخلف.

أمثلة شائعة:

  • إيجاد كل المسارات من الجذر للأوراق في شجرة ثنائية.

الفكرة: بنستخدم الاستدعاء الذاتي أو المكدس عشان نستكشف كل مسار في الرسوم البيانية أو الأشجار لحد نهايته.

مثال كود بايثون:

def dfs(root):
    if not root:
        return []
    return [root.val] + dfs(root.left) + dfs(root.right)

أسئلة LeetCode المرتبطة:

  1. Clone Graph

  2. Path Sum II

  3. Course Schedule II


12. Breadth-First Search (BFS) (البحث العرضي أولًا)

نمط BFS بنستخدمه لاستكشاف العقد في الرسوم البيانية أو الأشجار مستوى بمستوى. بنستخدم قائمة انتظار للاحتفاظ بالعقد اللي بدنا نستكشفها.

أمثلة شائعة:

  • إيجاد المسارات الأقصر في الرسوم البيانية غير الموزونة.

الفكرة: بنستخدم قائمة انتظار عشان نستكشف العقد في الرسوم البيانية أو الأشجار مستوى بمستوى، نبدأ من الجذر وبنكمل.

مثال كود بايثون:

from collections import deque

def bfs(root):
    result, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        result.append(node.val)
        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)
    return result

أسئلة LeetCode المرتبطة:

  1. Binary Tree Level Order Traversal

  2. Rotting Oranges

  3. Word Ladder


13. Matrix Traversal (التنقل في المصفوفات)

نمط Matrix Traversal بنستخدمه عشان نستكشف عناصر المصفوفة باستخدام تقنيات زي DFS أو BFS عشان نتنقل عبر العناصر أفقياً أو رأسياً أو حتى بشكل قطري.

أمثلة شائعة:

  • تعبئة الخلايا المتصلة في مصفوفة باستخدام خوارزمية Flood Fill.

الفكرة: بنستخدم DFS أو BFS للتنقل عبر المصفوفة واستكشاف كل العناصر المتصلة أو المتجاورة.

مثال كود بايثون:

def flood_fill(image, sr, sc, new_color):
    color = image[sr][sc]
    if color == new_color:
        return image

    def dfs(r, c):
        if image[r][c] == color:
            image[r][c] = new_color
            if r >= 1: dfs(r-1, c)
            if r+1 < len(image): dfs(r+1, c)
            if c >= 1: dfs(r, c-1)
            if c+1 < len(image[0]): dfs(r, c+1)

    dfs(sr, sc)
    return image

أسئلة LeetCode المرتبطة:

  1. Flood Fill

  2. Number of Islands

  3. Surrounded Regions


14. Backtracking (التراجع)

نمط Backtracking بيستكشف كل الحلول الممكنة وبتراجع عن المسار لما ما يقدر يوصل للحل المناسب. بنستخدمه لحل المشاكل اللي بتحتاج لتوليد كل الاحتمالات أو المجموع

ات الممكنة.

أمثلة شائعة:

  • توليد كل التباديل أو المجموعات من مجموعة أرقام معينة.

الفكرة: بنستخدم الاستدعاء الذاتي لتوليد كل الاحتمالات، وبنرجع للخلف لما يكون المسار مش مناسب.

مثال كود بايثون:

def permute(nums):
    result = []

    def backtrack(start=0):
        if start == len(nums):
            result.append(nums[:])
        for i in range(start, len(nums)):
            nums[start], nums[i] = nums[i], nums[start]
            backtrack(start + 1)
            nums[start], nums[i] = nums[i], nums[start]

    backtrack()
    return result

أسئلة LeetCode المرتبطة:

  1. Permutations

  2. Subsets

  3. N-Queens


15. Dynamic Programming (البرمجة الديناميكية)

Dynamic Programming بتعتمد على تقسيم المشكلة لأجزاء صغيرة وحلها بشكل متكرر باستخدام نهج من أعلى لأسفل أو من أسفل لأعلى. بنستخدمه لما المشكلة فيها حسابات مكررة أو متداخلة.

أمثلة شائعة:

  • حساب الأعداد الفيبوناتشي.

  • حل مشكلة Knapsack.

الفكرة: بنقسم المشكلة لأجزاء أصغر وبنخزن النتائج عشان ما نعيد الحساب.

مثال كود بايثون:

def fibonacci(n):
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]

أسئلة LeetCode المرتبطة:

  1. Climbing Stairs

  2. House Robber

  3. Coin Change

  4. Longest Increasing Subsequence


تعلم الأنماط البرمجية هو استثمار ممتاز في مشوارك كمبرمج. زي ما شفنا، هاي الأنماط مش مجرد أدوات لحل المشاكل، هي كمان مفتاح لفهم المشاكل بعمق وتوصلك للحلول بشكل أسرع وأدق. سواء كنت بتحل مسائل على منصات زي LeetCode أو بتحضر لمقابلة تقنية، تعلم هاي الأنماط رح يخليك تحل المشاكل اللي كانت تبين صعبة بسهولة.

المهم هو الاستمرار في الممارسة. كل ما تمرنت أكتر على هاي الأنماط، كل ما صرت أسرع في تحديد النمط المناسب لكل مشكلة. ولما تتقنها وتعرف كيف تطبقها صح، رح تقدر تحل أي مشكلة تقريبًا. مع الوقت، رح تلاقي نفسك بتنقل بسهولة بين المشاكل المعقدة والتحديات الجديدة بفضل معرفتك بالأنماط.

الشي المميز في هاي الأنماط إنها مش مرتبطة بنوع معين من المشاكل أو لغة برمجة محددة، هي أفكار خوارزمية بتقدر تطبقها على كتير مواقف وسيناريوهات. فبدل ما تكتفي بالفهم النظري، حاول دايمًا تطبق الأنماط في شغلك البرمجي اليومي.

الآن صار عندك فهم أعمق للأنماط، وتقدر تستخدم هالمعرفة لحل المشاكل بكفاءة أكتر. بس تذكر: التعلم مش بيخلص هنا، هذي بس البداية. في أنماط أكتر ومهارات خوارزمية أعقد لسه ممكن تتعلمها. استمر في الممارسة وما تخاف تواجه التحديات الأكبر.

وفي البوست الجاي، رح نحكي عن مشاكل شائعة في المقابلات التقنية. رح نركز على بعض الأسئلة اللي بتنطرح كتير في المقابلات وكيف ممكن نستخدم الأنماط والخوارزميات اللي تعلمناها عشان نحلها بفعالية. استعد تواجه أسئلة المقابلات بثقة!

0
Subscribe to my newsletter

Read articles from Salman Iyad directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Salman Iyad
Salman Iyad

I'm Salman Iyad, a passionate web developer with extensive experience in building robust applications using Next.js and React.js. My journey in tech is driven by a love for learning and problem-solving. I also have expertise in various technologies, including Node.js, MongoDB, and Tailwind CSS. Feel free to connect with me!