أهم الأنماط البرمجية لحل مشاكل المقابلات التقنية بكفاءة
لما نحكي عن البرمجة التنافسية أو المقابلات التقنية، بنلاحظ إنه المشكلة مش بعدد المسائل اللي بنحلها، بل في الأنماط البرمجية اللي بنتعلمها ونتقن استخدامها. تعلم الخوارزميات وأنماط البرمجة الفعّالة مش بس بيمكنك من حل مجموعة متنوعة من المشاكل بسرعة وكفاءة، كمان بيساعدك تفهم المشكلة بشكل أعمق وتختار الحل المناسب بطريقة أسرع.
في هالمقال رح أقدم 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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
11. Depth-First Search (DFS) (البحث العميق أولًا)
نمط DFS بنستخدمه لاستكشاف كل الفروع أو المسارات في الرسوم البيانية أو الأشجار عن طريق التوغل في العمق أولاً قبل الرجوع للخلف.
أمثلة شائعة:
- إيجاد كل المسارات من الجذر للأوراق في شجرة ثنائية.
الفكرة: بنستخدم الاستدعاء الذاتي أو المكدس عشان نستكشف كل مسار في الرسوم البيانية أو الأشجار لحد نهايته.
مثال كود بايثون:
def dfs(root):
if not root:
return []
return [root.val] + dfs(root.left) + dfs(root.right)
أسئلة LeetCode المرتبطة:
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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
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 المرتبطة:
تعلم الأنماط البرمجية هو استثمار ممتاز في مشوارك كمبرمج. زي ما شفنا، هاي الأنماط مش مجرد أدوات لحل المشاكل، هي كمان مفتاح لفهم المشاكل بعمق وتوصلك للحلول بشكل أسرع وأدق. سواء كنت بتحل مسائل على منصات زي LeetCode أو بتحضر لمقابلة تقنية، تعلم هاي الأنماط رح يخليك تحل المشاكل اللي كانت تبين صعبة بسهولة.
المهم هو الاستمرار في الممارسة. كل ما تمرنت أكتر على هاي الأنماط، كل ما صرت أسرع في تحديد النمط المناسب لكل مشكلة. ولما تتقنها وتعرف كيف تطبقها صح، رح تقدر تحل أي مشكلة تقريبًا. مع الوقت، رح تلاقي نفسك بتنقل بسهولة بين المشاكل المعقدة والتحديات الجديدة بفضل معرفتك بالأنماط.
الشي المميز في هاي الأنماط إنها مش مرتبطة بنوع معين من المشاكل أو لغة برمجة محددة، هي أفكار خوارزمية بتقدر تطبقها على كتير مواقف وسيناريوهات. فبدل ما تكتفي بالفهم النظري، حاول دايمًا تطبق الأنماط في شغلك البرمجي اليومي.
الآن صار عندك فهم أعمق للأنماط، وتقدر تستخدم هالمعرفة لحل المشاكل بكفاءة أكتر. بس تذكر: التعلم مش بيخلص هنا، هذي بس البداية. في أنماط أكتر ومهارات خوارزمية أعقد لسه ممكن تتعلمها. استمر في الممارسة وما تخاف تواجه التحديات الأكبر.
وفي البوست الجاي، رح نحكي عن مشاكل شائعة في المقابلات التقنية. رح نركز على بعض الأسئلة اللي بتنطرح كتير في المقابلات وكيف ممكن نستخدم الأنماط والخوارزميات اللي تعلمناها عشان نحلها بفعالية. استعد تواجه أسئلة المقابلات بثقة!
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!