Jump Game
Você está no índice 0 do array nums. A partir do índice i, você pode saltar para frente qualquer número de posições de 1 até nums[i], então nums[i] é o salto mais longo que você pode dar a partir daí, e 0 significa que você não pode se mover. Retorne true se alguma sequência de saltos chegar ao último índice e false caso contrário.
Função
- numsinteger-array
- o salto mais longo que você pode dar a partir de cada índice
- Retornaboolean
- true se você puder chegar ao último índice começando pelo índice 0; caso contrário, false
Restrições
1 ≤ nums.length ≤ 1040 ≤ nums[i] ≤ 105- Um salto pode ser mais curto que
nums[i], então um salto longo nunca força você a passar do último índice.
Exemplos
- Entrada
- nums = [2, 0, 3, 1, 0, 2]
- Saída
- true
- Explicação
- Do índice 0, você pode chegar ao índice 1 ou 2. O índice 1 contém 0 e é um beco sem saída, mas o índice 2 contém 3 e chega ao índice 5, o último índice.
- Entrada
- nums = [1, 3, 0, 0, 0, 2]
- Saída
- false
- Explicação
- O índice 0 só pode avançar para o índice 1, e o índice 1 alcança no máximo o índice 4. Os índices 2, 3 e 4 contêm 0, então nada passa do índice 4 para o índice 5.
- Entrada
- nums = [0]
- Saída
- true
- Explicação
- O array tem um elemento, então você começa no último índice e não precisa dar nenhum salto.
+18 testes ocultos ao enviar
Para ir além
Conte o número de diferentes sequências de saltos que chegam ao último índice, módulo 10^9+7, ainda em tempo O(n).
Dicas
Abra uma de cada vez. Cada uma revela um pouco mais.
Um
0só prende você quando nada antes dele consegue saltar por cima dele. O que você precisaria saber sobre os índices anteriores a ele para descobrir?Se você consegue chegar ao índice
i, consegue chegar a todos os índices deiatéi+nums[i], porque saltos mais curtos são permitidos. Portanto, os índices alcançáveis sempre formam um bloco contínuo que começa no índice 0.Percorra da esquerda para a direita e mantenha
farthest, a extremidade direita desse bloco. Se o índice atual estiver além defarthest, ele nunca poderá ser alcançado. Caso contrário, ampliefarthestparai+nums[i]quando esse valor for maior. Se a caminhada percorrer todo o array, o último índice poderá ser alcançado.
Solução
O número de rotas possíveis cresce exponencialmente, então verificar as rotas uma por uma não funciona em arrays longos. O fato essencial é que os índices que você pode alcançar sempre formam um bloco contínuo que começa no índice 0. Um único número, o limite direito desse bloco, contém tudo de que você precisa, e uma única passagem determina a resposta.
Experimente cada salto
Correta, mas não termina nos maiores testes
Intuição
A ideia mais direta é encenar o processo. Fique no índice 0 e tente, um por um, cada ponto de aterrissagem permitido pelo seu salto. A partir de cada ponto de aterrissagem, faça o mesmo novamente. Se algum ramo chegar ao último índice, a resposta é true. Se todos os ramos chegarem a um beco sem saída, é false.
No primeiro exemplo, o índice 0 contém 2, então você tenta os índices 1 e 2. O índice 1 contém 0, um beco sem saída, então você volta e tenta o índice 2. O índice 2 contém 3 e alcança o índice 5, o último índice, e a busca termina com true.
A busca está correta porque considera todas as rotas. Esse também é o seu problema: ela nunca se lembra de um índice que já explorou, então explora o mesmo índice novamente para cada rota que chega até ele. Quando a resposta é false, ela precisa descartar todas as rotas. Em [4, 3, 2, 1, 0, 5], todos os índices antes do 0 podem chegar ao 0, o que gera 8 rotas diferentes até ele. Com 30 índices desse tipo, há mais de 500 milhões de rotas, e os maiores testes têm 10,000 elementos. Uma rota tão longa também estoura a pilha de chamadas em algumas linguagens: por padrão, Python para após 1,000 chamadas aninhadas.
Algoritmo
- Escreva uma função auxiliar
reach(i)que responda: é possível chegar do índiceiao último índice? - Se
ifor o último índice, retornetrue. - Caso contrário, tente cada posição de chegada
nextdei+1atémin(i+nums[i], n-1)e retornetrueassim quereach(next)retornar esse valor. - Se nenhuma posição de chegada funcionar, retorne
false. - A resposta é
reach(0).
def canJump(nums):
last = len(nums) - 1
def reach(i):
# Can you get from index i to the last index?
if i == last:
return True
for nxt in range(i + 1, min(i + nums[i], last) + 1):
if reach(nxt):
return True
return False
return reach(0)Lembre-se de quais índices podem terminar
Correta, mas não termina nos maiores testes
Intuição
A busca acima faz a mesma pergunta, "o índice j consegue terminar?", repetidamente. A resposta para j nunca muda, então descubra-a uma vez e armazene-a. Chame um índice de bom quando for possível chegar dele ao último índice. O último índice é bom. Qualquer outro índice i é bom quando pelo menos um índice em que ele pode cair, de i+1 a i+nums[i], é bom.
Cada índice depende apenas dos índices à sua direita, então preencha uma tabela good da direita para a esquerda. No primeiro exemplo, o índice 5 é bom. O índice 4 contém 0, então não é. O índice 3 alcança apenas o índice 4: não é bom. O índice 2 alcança os índices 3, 4 e 5, e 5 é bom, então 2 é bom. O índice 1 contém 0: não é bom. O índice 0 alcança 1 e 2, e 2 é bom, então a resposta é true.
Agora cada índice é decidido uma única vez, mas para decidi-lo ainda pode ser necessário percorrer até n células. Em [9998, 9997, …, 1, 0, 7], cada índice pode alcançar o 0 e nada além dele, então cada um percorre todo o seu intervalo e não encontra nenhum índice bom. Isso dá cerca de 5 × 10^7 verificações para 10.000 elementos, e os maiores testes são construídos assim. O trabalho cresce com o quadrado do comprimento, então o tempo acaba antes que eles terminem.
Algoritmo
- Crie um array booleano
goodde comprimentone definagood[n-1]como true. - Percorra
iden-2até 0. - Percorra
jdei+1atémin(i+nums[i], n-1). Se algumgood[j]for true, definagood[i]como true e pare de percorrer. - Retorne
good[0].
def canJump(nums):
n = len(nums)
good = [False] * n # good[i]: from i you can reach the last index
good[n - 1] = True
for i in range(n - 2, -1, -1):
for j in range(i + 1, min(i + nums[i], n - 1) + 1):
if good[j]:
good[i] = True
break
return good[0]Acompanhe o índice mais distante que pode ser alcançado
Intuição
Observe quais índices você consegue alcançar, não as rotas. A partir do índice i, você pode chegar a qualquer índice de i+1 a i+nums[i], sem lacunas. Portanto, quando o índice i é alcançável, todos os índices até i+nums[i] também são alcançáveis. Comece apenas com o índice 0 e continue adicionando esses trechos. Cada novo trecho começa dentro do bloco que você já tem, então os índices alcançáveis sempre formam um único bloco contínuo, [0, farthest].
Por isso, basta um número. Percorra i da esquerda para a direita. Enquanto i ≤ farthest, o índice i é alcançável, então amplie farthest para max(farthest, i+nums[i]). Se i ultrapassar farthest, nenhum índice alcançável chega a i. O bloco não pode crescer além dessa lacuna, então nenhum índice à direita dela é alcançável, incluindo o último índice. Se a varredura chegar ao fim sem lacunas, o último índice é alcançável.
No segundo exemplo, farthest é 0, depois 1 após o índice 0 e, em seguida, 4 após o índice 1. Os índices 2, 3 e 4 contêm 0 e mantêm o valor em 4. O índice 5 está além de 4, então a resposta é false. No primeiro exemplo, o índice 2 leva farthest a 5 e nenhum índice fica além dele, então a resposta é true.
Por que é seguro manter apenas o maior alcance? Você nunca se compromete com um salto. O bloco contém todos os índices que qualquer rota pode alcançar, e todos os pontos de chegada mais próximos ficam dentro dele. Descartar tudo, exceto a extremidade direita, não faz você perder nenhuma informação.
Algoritmo
- Defina
farthest = 0. - Para cada índice
i, da esquerda para a direita: sei > farthest, retornefalse. - Caso contrário, defina
farthest = max(farthest, i+nums[i]). - Se o loop terminar, todos os índices eram alcançáveis, então retorne
true.
def canJump(nums):
farthest = 0 # every index up to farthest can be reached
for i, jump in enumerate(nums):
if i > farthest:
return False # nothing reachable jumps to i
farthest = max(farthest, i + jump)
return True
Armadilhas e casos extremos
A maioria das respostas erradas acontece por interpretar nums[i] como o único salto possível ou por causa da ordem das duas verificações dentro do loop.
- Sempre saltar exatamente
nums[i]posições ou sempre dar o salto mais longo. Com[2, 5, 0, 0], o salto completo a partir do índice 0 cai em um 0, enquanto o salto de 1 posição até o índice 1 alcança o final. - Retornar
falseassim que encontrar um 0. Um 0 só importa quando nada antes dele consegue saltar além dele:[2, 0, 1]salta por cima do 0, e a resposta étrue. - Atualizar
farthestantes de verificari > farthest. Um índice ao qual você não consegue chegar não deve ampliar o alcance, então verifique primeiro e depois atualize. - Tratar um array de um elemento como uma falha. Você já está no último índice, então a resposta é
true, mesmo quando esse elemento é 0. - Usar recursão em arrays longos. Um trajeto pode ter 10.000 saltos, o que estoura a pilha de chamadas em várias linguagens. A passagem única não usa recursão.
Perguntas frequentes4
Qual é a complexidade de tempo do Jump Game?
A passagem que alcança mais longe visita cada índice uma vez, então é executada em tempo O(n) com espaço extra O(1). A abordagem com tabela é O(n²) no pior caso, e tentar todas as rotas tem complexidade exponencial.
Por que a abordagem gulosa funciona para o Jump Game?
Como saltos menores são permitidos, chegar ao índice i significa que você pode alcançar todos os índices até i+nums[i]. Esses trechos sempre se sobrepõem à parte já alcançada, então os índices alcançáveis formam um único bloco que começa em 0. A passagem gulosa acompanha apenas a extremidade direita desse bloco, que descreve o bloco inteiro, então nunca descarta um caminho que poderia ter funcionado.
Jump Game é um problema de programação dinâmica?
Isso pode ser resolvido com programação dinâmica: marque cada índice como bom quando uma de suas posições de chegada for boa, preenchendo a tabela da direita para a esquerda. Isso custa O(n²). Observe que apenas o índice bom mais à esquerda importa, pois qualquer índice que alcance um índice bom também alcança o mais à esquerda. Mantenha apenas esse índice, goal, e mova-o para i sempre que i+nums[i] ≥ goal. A resposta é se goal termina em 0, uma passagem de O(n) que espelha a gulosa.
Como encontrar o número mínimo de saltos?
Use a mesma ideia de alcance máximo em camadas. Mantenha o fim do bloco que você consegue alcançar com o número atual de saltos e o índice mais distante que o próximo salto pode alcançar. Quando i passar do fim do bloco atual, você precisará de mais um salto, e o próximo bloco terminará nesse índice mais distante. Ainda é uma única passagem O(n).
Problemas parecidos
Problemas que usam as mesmas ideias. Resolver dois ou três é o que fixa um padrão.
Python
def canJump(nums):
# Escreva o código aquiCaso 1
Caso 2
Caso 3
Entrada
nums = [2, 0, 3, 1, 0, 2]
Esperado
true