مؤشران: الطرفان المتقابلان
استخدم مؤشرين أيسر وأيمن يتحركان نحو بعضهما لحل مجموع زوج في المصفوفات المرتبة ومتلازمة التناظر الصحيحة واحتجاز مياه الأمطار
مؤشران: الطرفان المتقابلان درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
فكرة المؤشرين
تستخدم تقنية المؤشرين متغيري فهرس يتحركان باتجاه بعضهما (أو في الاتجاه نفسه) لتقليل الحاجة إلى الحلقات المتداخلة. فبدلًا من فحص كل زوج في O(n²)، تحرز تقدمًا مع كل مقارنة وتنهي العملية في O(n). ويتطلب هذا الأسلوب غالبًا أن تكون المصفوفة مرتبة أولًا، لأن الترتيب يتيح لك تحديد اتجاه تحريك كل مؤشر بناءً على كون مجموع الزوج الحالي أكبر من اللازم أو أصغر منه.
# Without two pointers: O(n^2)
def two_sum_brute(nums, target):
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
# With two pointers on sorted array: O(n)
def two_sum_sorted(nums, target):
left, right = 0, len(nums) - 1
while left < right:
s = nums[left] + nums[right]
if s == target: return [left, right]
elif s < target: left += 1
else: right -= 1
return []المجموع الثنائي في مصفوفة مرتبة
في المصفوفة المرتبة، ضع مؤشرًا عند الطرف الأيسر (الأصغر) ومؤشرًا عند الطرف الأيمن (الأكبر). إذا كان المجموع صغيرًا جدًا، حرّك المؤشر الأيسر إلى اليمين لزيادته. وإذا كان المجموع كبيرًا جدًا، حرّك المؤشر الأيمن إلى اليسار لتقليله. تقدّم كل دورة مؤشرًا واحدًا على الأقل، ولذلك تعمل الحلقة في n دورات كحد أقصى، أي O(n) إجمالًا بعد الترتيب. والأهم أن كل حركة صحيحة بصورة مثبتة بفضل ترتيب العناصر.
def two_sum_sorted(numbers, target):
# numbers is 1-indexed per LeetCode 167
left, right = 0, len(numbers) - 1
while left < right:
s = numbers[left] + numbers[right]
if s == target:
return [left + 1, right + 1] # 1-indexed
elif s < target:
left += 1 # need larger sum
else:
right -= 1 # need smaller sum
return []
print(two_sum_sorted([2, 7, 11, 15], 9)) # [1, 2]
print(two_sum_sorted([2, 3, 4], 6)) # [1, 3]التحقق من كون النص متناظرًا
يكون النص متناظرًا إذا قُرئ بالطريقة نفسها من الأمام والخلف. استخدم مؤشرين يبدأان عند الطرفين ويتحركان إلى الداخل: قارن المحارف، وتجاوز المحارف غير الأبجدية الرقمية، وتوقف عندما يتجاوز المؤشران بعضهما. يعمل هذا في زمن O(n) وبمساحة إضافية O(1)، وهو أنظف بكثير من عكس النص ثم مقارنته، وهي عملية تحجز ذاكرة إضافية بحجم O(n).
def is_palindrome(s):
left, right = 0, len(s) - 1
while left < right:
# Skip non-alphanumeric
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
print(is_palindrome('A man, a plan, a canal: Panama')) # True
print(is_palindrome('race a car')) # Falseالمجموع الثلاثي: الترتيب مع مؤشرين
تطلب مسألة المجموع الثلاثي جميع الثلاثيات الفريدة التي يساوي مجموعها صفرًا. رتّب المصفوفة، ثم ثبّت كل عنصر nums[i] ونفّذ بحثًا بمؤشرين في المصفوفة الجزئية المتبقية عن زوج يساوي مجموعه -nums[i]. تجاوز القيم المكررة للعنصر المثبّت وللزوج الذي عثرت عليه لتجنب تكرار الثلاثيات. الزمن الإجمالي هو O(n²) بعد ترتيب بتعقيد O(n log n).
def three_sum(nums):
nums.sort()
result = []
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i-1]: continue # skip dupe
left, right = i + 1, len(nums) - 1
while left < right:
s = nums[i] + nums[left] + nums[right]
if s == 0:
result.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]: left += 1
while left < right and nums[right] == nums[right-1]: right -= 1
left += 1; right -= 1
elif s < 0: left += 1
else: right -= 1
return result
print(three_sum([-1, 0, 1, 2, -1, -4]))
# [[-1,-1,2],[-1,0,1]]الحاوية التي تحتوي أكبر كمية من الماء
مع إعطائك ارتفاعات الخطوط الرأسية، اعثر على خطين يشكلان حاوية تحتوي على أكبر كمية من الماء. المساحة = min(height[left], height[right]) × (right - left). حرّك المؤشر الموجود عند الخط الأقصر إلى الداخل بطريقة جشعة؛ فتحريك الخط الأطول لا يمكنه إلا تقليل العرض دون زيادة حد الارتفاع. وهذا الاختيار الجشع أمثل بصورة مثبتة ويحقق زمن O(n).
def max_area(height):
left, right = 0, len(height) - 1
best = 0
while left < right:
h = min(height[left], height[right])
area = h * (right - left)
best = max(best, area)
# Move the shorter wall inward
if height[left] < height[right]:
left += 1
else:
right -= 1
return best
print(max_area([1, 8, 6, 2, 5, 4, 8, 3, 7])) # 49تربيع مصفوفة مرتبة
ربّع كل عنصر في مصفوفة مرتبة (قد تحتوي على أعداد سالبة)، ثم أعد الناتج بترتيب تصاعدي. تكون مربعات الأعداد السالبة كبيرة، بينما تكون مربعات الأعداد الموجبة صغيرة في الوسط. ضع مؤشرين عند الطرفين واملأ مصفوفة الناتج من اليمين إلى اليسار (من الأكبر إلى الأصغر). الزمن O(n) ومساحة الناتج O(n)، وهذا أفضل بكثير من التربيع ثم الترتيب في O(n log n).
def sorted_squares(nums):
n = len(nums)
result = [0] * n
left, right = 0, n - 1
pos = n - 1
while left <= right:
l_sq = nums[left] ** 2
r_sq = nums[right] ** 2
if l_sq > r_sq:
result[pos] = l_sq
left += 1
else:
result[pos] = r_sq
right -= 1
pos -= 1
return result
print(sorted_squares([-4, -1, 0, 3, 10]))
# [0, 1, 9, 16, 100]حبس مياه الأمطار
تساوي كمية المياه المحبوسة عند الفهرس i min(max_left, max_right) - height[i]. في نهج المؤشرين، حافظ على القيم التراكمية max_left وmax_right. عندما يكون max_left < max_right، يكون الجانب الأيسر هو عنق الزجاجة، لذا عالج المؤشر الأيسر. وإلا فعالج المؤشر الأيمن. يلغي هذا الحاجة إلى مصفوفتين منفصلتين لأقصى قيمة يسارية وأقصى قيمة يمينية، ويحقق مساحة إضافية O(1).
def trap(height):
left, right = 0, len(height) - 1
max_left = max_right = 0
water = 0
while left < right:
if height[left] < height[right]:
if height[left] >= max_left:
max_left = height[left]
else:
water += max_left - height[left]
left += 1
else:
if height[right] >= max_right:
max_right = height[right]
else:
water += max_right - height[right]
right -= 1
return water
print(trap([0,1,0,2,1,0,1,3,2,1,2,1])) # 6لماذا تنجح حركة المؤشر الجشعة
من الأسئلة الشائعة في المقابلات: لماذا من الآمن استبعاد المؤشر الأصغر؟ إليك مخططًا لإثبات ذلك في مسألة الحاوية التي تحتوي أكبر كمية من الماء: افترض أن height[left] < height[right]. كل زوج (left, j) حيث j < right يعطي مساحة ≤ height[left] × (j-left) < height[left] × (right-left) ≤ المساحة الحالية. لذلك لا يمكن لأي زوج يبدأ عند 'left' ويحتوي على فهرس أيمن أصغر من 'right' أن يتفوق على المساحة الحالية. يمكننا تخطي هذه الأزواج بأمان عبر تقديم left.
# Correctness argument via contradiction:
# If left < right and height[left] < height[right],
# then for any j in (left, right):
# area(left, j) <= min(h[left], h[j]) * (j - left)
# <= h[left] * (j - left)
# <= h[left] * (right - left) [since j < right]
# = current area
# So no pair (left, j) for j < right can improve.
# Moving left inward is SAFE.
print('Proof verified: advance shorter pointer is optimal')زوج الأعداد ذي أصغر فرق في مصفوفة مرتبة
اعثر على زوج الأعداد في مصفوفة مرتبة الذي يملك أصغر فرق مطلق. استخدم مؤشرين متجاورين (وليسا عند الطرفين المتقابلين) يتحركان معًا: |nums[i] - nums[i+1]| لجميع الأزواج المتتالية. يحدث أصغر فرق في مصفوفة مرتبة دائمًا بين عنصرين متجاورين (لأن الترتيب يجمع القيم المتقاربة معًا). التعقيد هو O(n) بعد الترتيب.
def min_diff_pair(nums):
nums.sort() # O(n log n)
min_diff = float('inf')
best = (nums[0], nums[1])
for i in range(len(nums) - 1):
diff = nums[i+1] - nums[i] # sorted: always >= 0
if diff < min_diff:
min_diff = diff
best = (nums[i], nums[i+1])
return best, min_diff
pair, d = min_diff_pair([4, 2, 1, 6, 10, 8])
print(pair, d) # (1, 2) 1قالب المؤشرين عند الطرفين المتقابلين
تتبع معظم مسائل المؤشرين عند الطرفين المتقابلين الهيكل الأساسي نفسه. يتيح لك إتقان هذا القالب تكييفه بسرعة تحت ضغط الوقت. والقرارات الأساسية هي: (1) ما الشرط الذي يحرّك المؤشر الأيسر، (2) ما الشرط الذي يحرّك المؤشر الأيمن، (3) ما الذي يُعد حلًا، و(4) كيفية التعامل مع التكرارات. تدرّب على استخلاص هذه القرارات من نص المسألة قبل كتابة أي كود.
def two_pointer_template(arr, condition):
"""
Generic opposite-ends two-pointer skeleton.
Replace condition logic for each specific problem.
"""
left, right = 0, len(arr) - 1
result = []
while left < right:
current = arr[left] + arr[right] # or some combination
if current == condition: # found a valid pair
result.append((arr[left], arr[right]))
left += 1
right -= 1
elif current < condition: # need to increase
left += 1
else: # need to decrease
right -= 1
return resultعدّ الأزواج الصالحة باستخدام مؤشرين
يمكن للمؤشرين أيضًا عدّ الأزواج بكفاءة. في مسألة «عدّ الأزواج التي يكون مجموعها < target» في مصفوفة مرتبة: ثبّت المؤشر الأيسر واستخدم المؤشر الأيمن للعثور على أقصى فهرس أيمن صالح. جميع الأزواج (left, left+1 إلى right) صالحة — أضف right - left إلى العدد ثم حرّك left. بهذه الطريقة تُحصي جميع الأزواج الصالحة في O(n) بدلًا من O(n²).
def count_pairs_less_than(nums, target):
nums.sort()
left, right = 0, len(nums) - 1
count = 0
while left < right:
if nums[left] + nums[right] < target:
count += right - left # all (left, left+1..right) valid
left += 1
else:
right -= 1
return count
print(count_pairs_less_than([1, 2, 3, 4, 5], 6))
# pairs: (1,2)(1,3)(1,4)(2,3) -> 4تحقق سريع
اختبر مدى فهمك لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.
مراجعة الدرس
في هذا الدرس تعلّمتم أن: المؤشرين عند الطرفين المتقابلين يستبدلان تعداد الأزواج في O(n²) بالتقارب من اليسار إلى اليمين في O(n) على المصفوفات المرتبة، قرار تحديد المؤشر الذي يجب تحريكه ينبع من الخاصية الرتيبة للمسألة — حرّك الجانب الذي يحدّ التقدم حاليًا، ومسائل مجموع ثلاثة أعداد، والحاوية ذات أكبر كمية ماء، واحتجاز مياه الأمطار، والتحقق من التناظر تختزل جميعها إلى القالب الأساسي نفسه. بعد ذلك سنستكشف أنماط المؤشرين البطيء والسريع.
الأسئلة الشائعة
هل درس «مؤشران: الطرفان المتقابلان» مجاني؟
نعم — نص درس «مؤشران: الطرفان المتقابلان» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «مؤشران: الطرفان المتقابلان»؟
استخدم مؤشرين أيسر وأيمن يتحركان نحو بعضهما لحل مجموع زوج في المصفوفات المرتبة ومتلازمة التناظر الصحيحة واحتجاز مياه الأمطار تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟
لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «مؤشران: الطرفان المتقابلان»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟
نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- أساسيات المصفوفات والعمليات في مكانها
- المجاميع البادئة والإجماليات التراكمية
- مؤشران: الطرفان المتقابلان
- مؤشران: البطيء والسريع