لعبة القفز I وII
حدّد إمكانية الوصول وعدد القفزات الأدنى باستخدام نهج جشع يوسّع النطاق، من دون الحاجة إلى البرمجة الديناميكية.
لعبة القفز I وII درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
Jump Game I: هل يمكنك الوصول إلى النهاية؟
Jump Game I (LeetCode 55): لديك مصفوفة تكون فيها nums[i] هي أقصى طول للقفزة انطلاقًا من الفهرس i، والمطلوب تحديد ما إذا كان بإمكانك الوصول إلى الفهرس الأخير بدءًا من الفهرس 0. بالنسبة إلى [2, 3, 1, 1, 4]، يمكنك الوصول إلى النهاية (اقفز من 2→3، ثم توصلك 3 إلى النهاية). أما بالنسبة إلى [3, 2, 1, 0, 4]، فلا يمكنك ذلك (فستهبط دائمًا على 0، الذي طول قفزته 0). تعمل الخوارزمية الجشعة في O(n).
# Can you reach the last index?
nums1 = [2, 3, 1, 1, 4] # True: 0→1→4 or 0→2→3→4
nums2 = [3, 2, 1, 0, 4] # False: always land on index 3 (value 0)
# At index 3 (value 0): no matter how you get here,
# you can't jump further to reach index 4
print('nums1 last index:', len(nums1)-1)
print('nums2 index 3 jump value:', nums2[3]) # 0 = stuckالخوارزمية الجشعة: تتبّع أقصى وصول
الفكرة الجشعة في Jump Game I هي الحفاظ على max_reach، أي أبعد فهرس يمكن الوصول إليه حتى الآن. عند كل فهرس i، حدّث max_reach = max(max_reach, i + nums[i]). إذا تحقق في أي نقطة أن i > max_reach، فالفهرس الحالي غير قابل للوصول — أعد False. وإذا وصلنا إلى الفهرس الأخير أو تجاوزناه، فأعد True. لا حاجة إلى البرمجة الديناميكية أو التراجع.
def can_jump(nums):
max_reach = 0
for i, jump in enumerate(nums):
if i > max_reach: # can't reach index i
return False
max_reach = max(max_reach, i + jump)
if max_reach >= len(nums) - 1:
return True # early exit
return True
print(can_jump([2, 3, 1, 1, 4])) # True
print(can_jump([3, 2, 1, 0, 4])) # False
print(can_jump([0])) # True (already at last index)
print(can_jump([1, 0, 0])) # Falseتتبّع Jump Game I
تتبّع [3, 2, 1, 0, 4]: i=0, jump=3, max_reach=3. i=1, jump=2, max_reach=max(3,3)=3. i=2, jump=1, max_reach=max(3,3)=3. i=3, jump=0, max_reach=max(3,3)=3. i=4, i=4 > max_reach=3 → return False. تحدد الخوارزمية بصورة صحيحة أن الفهرس 4 غير قابل للوصول. كل مسار يبدأ من الفهرس 0 يظل عالقًا، لأن القيمة 0 في الفهرس 3 تحدّ أقصى وصول إلى 3.
def can_jump_trace(nums):
max_reach = 0
for i, jump in enumerate(nums):
print(f'i={i}, jump={jump}, max_reach before={max_reach}', end='')
if i > max_reach:
print(' → UNREACHABLE')
return False
max_reach = max(max_reach, i + jump)
print(f' → max_reach={max_reach}')
return True
print('Result:', can_jump_trace([3, 2, 1, 0, 4]))Jump Game II: الحد الأدنى من القفزات
Jump Game II (LeetCode 45) تطلب إيجاد الحد الأدنى لعدد القفزات للوصول إلى الفهرس الأخير (وهو قابل للوصول دائمًا). يستخدم الأسلوب الجشع استراتيجية توسيع النطاق: نحافظ على أبعد وصول للقفزة الحالية (curr_end) وأبعد وصول للقفزة التالية (farthest). عندما نستنفد نطاق القفزة الحالية، يجب تنفيذ قفزة — زد jumps بمقدار 1 واضبط curr_end = farthest.
def jump(nums):
n = len(nums)
if n == 1: return 0 # already at destination
jumps = 0
curr_end = 0 # end of current jump's range
farthest = 0 # farthest reachable in next jump
for i in range(n - 1): # don't jump from last index
farthest = max(farthest, i + nums[i])
if i == curr_end: # exhausted current jump range
jumps += 1
curr_end = farthest
if curr_end >= n - 1: break
return jumps
print(jump([2, 3, 1, 1, 4])) # 2 (0→1→4)
print(jump([2, 3, 0, 1, 4])) # 2 (0→1→4)
print(jump([1, 2, 1, 1, 1])) # 3تصوير Jump Game II
فكّر في Jump Game II على أنه أسلوب اجتياز BFS مستوى بعد مستوى من دون الكلفة الإضافية للطابور. تمثل كل قفزة مستوى من مستويات BFS. يمثّل curr_end حدّ المستوى الحالي، بينما يمثّل farthest أكبر فهرس يمكن الوصول إليه في المستوى التالي. عند الانتهاء من فحص المستوى الحالي (i == curr_end)، نكون قد حددنا حدّ المستوى التالي، ويجب زيادة عدد القفزات. هذه عملية BFS على رسم بياني ضمني في O(n) من الزمن وO(1) من المساحة.
def jump_traced(nums):
n = len(nums)
jumps = curr_end = farthest = 0
for i in range(n - 1):
farthest = max(farthest, i + nums[i])
print(f'i={i}: farthest={farthest}, curr_end={curr_end}')
if i == curr_end:
jumps += 1
curr_end = farthest
print(f' → JUMP #{jumps}, new range ends at {curr_end}')
if curr_end >= n - 1: break
return jumps
print('Min jumps:', jump_traced([2, 3, 1, 1, 4]))لماذا تصح الخوارزمية الجشعة في Jump Game II
لماذا يؤدي الأسلوب الجشع (أي التمدد دائمًا إلى أبعد نقطة) إلى أقل عدد من القفزات؟ حجة الاستبدال: افترض أن الحل الأمثل ينفذ قفزة لا تصل إلى أبعد نقطة. يمكننا دائمًا تمديد تلك القفزة لتصل إلى farthest من دون أي تكلفة إضافية — فما زالت قفزة واحدة. وباختيار أكبر نطاق في كل قفزة، نضمن الحد الأدنى لعدد القفزات المطلوبة. وأي حل يقطع نطاقًا أصغر في كل قفزة لا يمكنه تحقيق نتيجة أفضل، وسيحتاج إلى قفزات أكثر لقطع المسافة نفسها.
# Correctness verification: compare to BFS
from collections import deque
def jump_bfs(nums):
n = len(nums)
if n == 1: return 0
visited = [False] * n
visited[0] = True
queue = deque([0])
level = 0
while queue:
level += 1
for _ in range(len(queue)):
pos = queue.popleft()
for j in range(1, nums[pos] + 1):
nxt = pos + j
if nxt >= n - 1: return level
if not visited[nxt]:
visited[nxt] = True
queue.append(nxt)
return -1
# Both should give same results
for nums in [[2,3,1,1,4],[2,3,0,1,4],[1,2,1,1,1]]:
print(jump(nums), '==', jump_bfs(nums))بديل DP لـ Jump Game II
حل باستخدام DP: تمثل dp[i] الحد الأدنى لعدد القفزات للوصول إلى الفهرس i. لكل موضع j، حدّث جميع المواضع القابلة للوصول: dp[j+k] = min(dp[j+k], dp[j]+1) لكل k ضمن 1..nums[j]. يعمل هذا الحل في O(n × max_jump) من الزمن وO(n) من المساحة، وهو أبطأ بكثير من الحل الجشع ذي التعقيد O(n). الحل الجشع أفضل هنا؛ ونوضح DP للمقارنة، لبيان كيف يمكن للأسلوب الجشع تجنب الحلقة الداخلية.
def jump_dp(nums):
n = len(nums)
dp = [float('inf')] * n
dp[0] = 0
for j in range(n):
for k in range(1, nums[j] + 1):
if j + k < n:
dp[j+k] = min(dp[j+k], dp[j] + 1)
return dp[n-1]
print(jump_dp([2, 3, 1, 1, 4])) # 2
print(jump_dp([1, 2, 1, 1, 1])) # 3
# Greedy is O(n), DP is O(n * max_jump)
# For large inputs with big jump values, greedy is much fasterJump Game III: الوصول إلى الفهرس ذي القيمة صفر
Jump Game III (LeetCode 1306): ابدأ من فهرس محدد؛ ومن الفهرس i، اقفز إلى i + nums[i] أو i - nums[i]. هل يمكنك الوصول إلى أي فهرس قيمته 0؟ هذه مسألة تتعلق بإمكانية الوصول (BFS/DFS)، وليست مسألة تقليل — لذلك لا تنطبق الخوارزمية الجشعة. استخدم BFS مع مجموعة visited لتجنب الدورات. التعقيد الزمني: O(n).
from collections import deque
def can_reach(arr, start):
n = len(arr)
visited = set()
queue = deque([start])
while queue:
idx = queue.popleft()
if arr[idx] == 0: return True
if idx in visited: continue
visited.add(idx)
for nxt in [idx + arr[idx], idx - arr[idx]]:
if 0 <= nxt < n and nxt not in visited:
queue.append(nxt)
return False
print(can_reach([4,2,3,0,3,1,2], 5)) # True (5→4→1→3, arr[3]=0)
print(can_reach([3,0,2,1,2], 2)) # False (can't reach index 1, arr[1]=0)Jump Game VII: إمكانية الوصول باستخدام نطاق
Jump Game VII (LeetCode 1871): هل يمكنك اجتياز سلسلة ثنائية بالقفز من الفهرس 0 إلى الفهرس الأخير، بحيث يمكنك من الموضع i القفز إلى أي '0' ضمن [i+minJump, i+maxJump]؟ استخدم مجموع نافذة منزلقة على مصفوفة المواضع القابلة للوصول. حافظ على مجموع تراكمي للمواضع القابلة للوصول؛ يكون الموضع j قابلًا للوصول إذا وُجد موضع قابل للوصول ضمن [j-maxJump, j-minJump].
def can_reach_vii(s, min_jump, max_jump):
n = len(s)
reach = [False] * n
reach[0] = True
pre = [0] * (n + 1) # prefix sum of reachable positions
pre[1] = 1
for j in range(1, n):
# Window sum: any reachable position in [j-maxJump, j-minJump]?
lo = max(0, j - max_jump)
hi = max(0, j - min_jump + 1)
window_sum = pre[hi] - pre[lo]
if s[j] == '0' and window_sum > 0:
reach[j] = True
pre[j+1] = pre[j] + (1 if reach[j] else 0)
return reach[n-1]
print(can_reach_vii('011010', 2, 3)) # True
print(can_reach_vii('01101110', 2, 3)) # Falseمقارنة بين حلول الجشع وBFS
لدى Jump Game II أسلوبان متكافئان بتعقيد O(n): توسيع النطاق الجشع واجتياز المستويات باستخدام BFS. يستخدم الأسلوب الجشع O(1) من المساحة (من دون طابور)، بينما يستخدم BFS مقدار O(n) بسبب مجموعة visited. في مقابلة، يُفضّل الأسلوب الجشع لكفاءته من حيث المساحة. ومع ذلك، يسهل اشتقاق BFS أولًا — فإذا واجهت صعوبة في رؤية الحل الجشع، فاكتب حل BFS للحصول على حل عملي، ثم حسّنه. كلا الأسلوبين يحسب الحد الأدنى لعدد القفزات بصورة صحيحة.
# Both approaches are O(n) time
# Greedy: O(1) space — preferred in interviews
# BFS: O(n) space — easier to derive
# Greedy advantage: no auxiliary data structures
def jump_greedy(nums):
n, jumps, curr, far = len(nums), 0, 0, 0
for i in range(n-1):
far = max(far, i+nums[i])
if i == curr: jumps += 1; curr = far
return jumps
# BFS equivalence: each level = one jump
from collections import deque
def jump_bfs(nums):
n = len(nums)
if n == 1: return 0
q, visited, level = deque([0]), {0}, 0
while q:
level += 1
for _ in range(len(q)):
pos = q.popleft()
for j in range(1, nums[pos]+1):
nxt = pos + j
if nxt >= n-1: return level
if nxt not in visited: visited.add(nxt); q.append(nxt)
return -1
nums = [2,3,1,1,4]
print(jump_greedy(nums), '==', jump_bfs(nums)) # both 2ملخص تعقيد Jump Game
ملخص التعقيد عبر تنويعات Jump Game: Jump I (إمكانية الوصول): زمن O(n)، ومساحة O(1). Jump II (الحد الأدنى من القفزات بالأسلوب الجشع): زمن O(n)، ومساحة O(1). Jump II (BFS): زمن O(n)، ومساحة O(n). Jump II (DP): زمن O(n × max_jump)، ومساحة O(n). Jump III (BFS/DFS): زمن O(n)، ومساحة O(n) لمجموعة visited. Jump VII (النافذة المنزلقة): زمن O(n)، ومساحة O(n). احرص دائمًا على عرض الحل الجشع ذي التعقيد O(n) زمنيًا وO(1) مكانيًا لـ Jump I وJump II في المقابلات.
# Comparison: all versions on the same input
nums = [2, 3, 1, 1, 4]
# Jump I
def can_jump(nums):
mr = 0
for i, j in enumerate(nums):
if i > mr: return False
mr = max(mr, i+j)
return True
# Jump II greedy O(n) O(1)
def jump_min(nums):
n, jumps, curr, far = len(nums), 0, 0, 0
for i in range(n-1):
far = max(far, i+nums[i])
if i == curr:
jumps += 1; curr = far
if curr >= n-1: break
return jumps
print('Can reach:', can_jump(nums)) # True
print('Min jumps:', jump_min(nums)) # 2
print('Complexity: O(n) time, O(1) space')اختبار سريع
اختبر مدى فهمك لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.
مراجعة الدرس
في هذا الدرس تعلمت: يستخدم Jump Game I تتبّع max_reach بأسلوب جشع لتحديد إمكانية الوصول في زمن O(n) ومساحة O(1)، ويستخدم Jump Game II توسيع النطاق مع curr_end وfarthest لحساب الحد الأدنى من القفزات في زمن O(n) ومساحة O(1)، وأن توسيع النطاق الجشع يكافئ اجتياز BFS مستوى بعد مستوى من دون الكلفة الإضافية للطابور. بعد ذلك سنطبّق التفكير الجشع على فترة التبريد في Task Scheduler ومسائل إمكانية إكمال المسار الدائري في Gas Station.
الأسئلة الشائعة
هل درس «لعبة القفز I وII» مجاني؟
نعم — نص درس «لعبة القفز I وII» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «لعبة القفز I وII»؟
حدّد إمكانية الوصول وعدد القفزات الأدنى باستخدام نهج جشع يوسّع النطاق، من دون الحاجة إلى البرمجة الديناميكية. تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟
لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «لعبة القفز I وII»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟
نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- الخوارزميات الجشعة مقابل البرمجة الديناميكية: متى تستخدم كلًّا منهما
- جدولة الفواصل ودمجها
- لعبة القفز I وII
- جدولة المهام ومحطة الوقود