Игра с прыжками I и II
Определяйте достижимость и минимальное число прыжков с помощью жадного расширения диапазона, не прибегая к DP
«Игра с прыжками I и II» — бесплатный урок DSA Interview Prep на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DSA Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DSA Interview Prep содержит 4 уроков всего.
Игра с прыжками I: можно ли достичь конца?
Игра с прыжками I (LeetCode 55): дан массив, в котором nums[i] — максимальная длина jump из индекса i. Определите, можно ли достичь последнего индекса, начав с индекса 0. Для [2, 3, 1, 1, 4] конец достижим (jump 2→3, затем из 3 можно попасть в конец). Для [3, 2, 1, 0, 4] это невозможно (Вы всегда попадаете на 0, у которого jump равен 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Жадный подход: отслеживание максимальной достижимости
Жадная идея для Игры с прыжками I: поддерживайте max_reach — самый дальний индекс, достижимый на данный момент. На каждом индексе i обновляйте max_reach = max(max_reach, i + nums[i]). Если в какой-то момент i > max_reach, текущий индекс недостижим — верните ложное значение. Если Вы достигли последнего индекса или перешли его, верните истинное значение. DP и возврат с возвратом не нужны.
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Трассировка Игры с прыжками 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 → верните ложное значение. Алгоритм правильно определяет, что индекс 4 недостижим. Любой путь из индекса 0 оказывается в ловушке, поскольку 0 на индексе 3 ограничивает max_reach значением 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]))Игра с прыжками II: минимальное число прыжков
Игра с прыжками II (LeetCode 45) требует найти минимальное число прыжков до последнего индекса (он всегда достижим). Жадный подход использует стратегию расширения диапазона: поддерживайте самую дальнюю достижимость текущего jump (curr_end) и самую дальнюю достижимость следующего jump (farthest). Когда диапазон текущего jump исчерпан, необходимо выполнить jump — увеличить jumps и установить 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Визуализация Игры с прыжками II
Представьте Игру с прыжками II как подход BFS по уровням, но без накладных расходов на очередь. Каждый jump соответствует одному уровню 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]))Почему жадный подход корректен для Игры с прыжками II
Почему жадный подход, всегда расширяющий диапазон до самой дальней точки, даёт минимальное число прыжков? Обменный аргумент: предположим, что оптимальное решение выполняет jump, не достигающий самой дальней точки. Такой jump всегда можно продлить до farthest без дополнительных затрат — это по-прежнему один jump. Выбирая максимальный диапазон для каждого jump, мы гарантируем минимальное необходимое число прыжков. Решение, охватывающее меньший диапазон за один jump, не может быть лучше и потребует больше прыжков, чтобы покрыть то же расстояние.
# 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 для Игры с прыжками 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 fasterИгра с прыжками III: достижимость индекса 0
Игра с прыжками III (LeetCode 1306): начните с заданного индекса; из индекса i можно перейти на i + nums[i] или i - nums[i]. Можно ли достичь какого-либо индекса со значением 0? Это задача на достижимость (BFS/DFS), а не на минимизацию — жадный подход здесь не применим. Используйте BFS с множеством посещённых индексов, чтобы избежать циклов. Временная сложность — 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)Игра с прыжками VII: достижимость в заданном диапазоне
Игра с прыжками 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
Для Игры с прыжками II существуют два эквивалентных подхода со сложностью O(n): жадное расширение диапазона и обход уровней с помощью BFS. Жадный подход использует O(1) памяти, поскольку очередь не нужна, тогда как BFS использует O(n) памяти для множества посещённых позиций. На собеседовании предпочтителен жадный подход благодаря эффективному использованию памяти. Однако 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Итоги по сложности задач на прыжки
Итоги по сложности вариантов Игры с прыжками: Игра I (достижимость): время O(n), память O(1). Игра II (жадный подсчёт прыжков): время O(n), память O(1). Игра II (BFS): время O(n), память O(n). Игра II (DP): время O(n × max_jump), память O(n). Игра III (BFS/DFS): время O(n), память O(n) для множества посещённых позиций. Игра VII (скользящее окно): время O(n), память O(n). На собеседованиях всегда представляйте жадное решение O(n) и O(1) для Игр I и 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')Быстрая проверка
Проверьте, насколько хорошо Вы поняли концепции «Структуры данных & алгоритмы — подготовка к собеседованию по программированию» из этого урока.
Итоги урока
В этом уроке Вы узнали: как Игра с прыжками I использует отслеживание max_reach жадным методом для определения достижимости за время O(n) и при использовании O(1) памяти, как Игра с прыжками II использует расширение диапазона с curr_end и farthest для подсчёта минимального числа прыжков за O(n) и при использовании O(1) памяти, а также что жадное расширение диапазона эквивалентно BFS по уровням без накладных расходов на очередь. Далее мы применим жадное рассуждение к задаче планировщика задач с периодом охлаждения и задаче о круговом маршруте между автозаправочными станциями.
Часто задаваемые вопросы
Урок «Игра с прыжками I и II» бесплатный?
Да — полный текст урока «Игра с прыжками I и II» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DSA Interview Prep, подпишись на CoddyKit PRO. Курс DSA Interview Prep содержит 4 уроков всего.
Чему я научусь в уроке «Игра с прыжками I и II»?
Определяйте достижимость и минимальное число прыжков с помощью жадного расширения диапазона, не прибегая к DP Ты практикуешь DSA Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать DSA Interview Prep?
Предыдущий опыт не требуется. DSA Interview Prep на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Игра с прыжками I и II»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке DSA Interview Prep?
Да. Каждый урок DSA Interview Prep включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Жадные алгоритмы и DP: когда что использовать
- Планирование и объединение интервалов
- Игра с прыжками I и II
- Планировщик задач и заправочная станция