Pular para o conteudo principal

Ladrão de casas

Como modelar a escolha entre roubar ou pular cada casa respeitando a restrição de adjacência.

Você e um ladrao planejando roubar casas ao longo de uma rua.

Cada casa tem uma quantidade de dinheiro, dada por nums[i].

Casas adjacentes não podem ser roubadas na mesma noite.

Retorne o valor máximo que você consegue roubar.

Exemplo:

Input:  nums = [2,7,9,3,1]
Output: 12

Porque a melhor escolha e 2 + 9 + 1 = 12.

O que perceber antes de codar

Erros comuns

Passo 1 - Ver por que o guloso local falha

Se você só olha a casa atual, pode pensar:

  • pego a maior que estiver na frente

Mas isso quebra fácil.

O problema não e escolher uma casa boa agora.

E escolher uma sequência boa sem vizinhas.

Passo 2 - Escrever a versão inicial recursiva

Para uma casa no indice i, você tem duas opcoes:

  • pular i e ir para i + 1
  • roubar i e ir para i + 2

Isso gera uma recursão natural.

Funciona, mas recalcula muitos indices.

Passo 3 - Montar a recorrencia certa

Olhando da esquerda para a direita, a melhor resposta até a casa i e:

  • ou a melhor resposta até i - 1
  • ou a melhor resposta até i - 2 mais nums[i]

Entao:

best(i) = max(best(i - 1), best(i - 2) + nums[i])

Passo 4 - Guardar só os dois estados anteriores

Você não precisa de uma tabela inteira.

Só precisa lembrar:

  • a melhor resposta até a casa anterior
  • a melhor resposta até duas casas antes

O editor interativo precisa de JavaScript. Voce ainda pode ler o desafio e copiar o codigo inicial abaixo.

Codigo inicial

export function rob(nums: number[]): number {
  // sua solução aqui
  return 0
}
solution.ts
Travado? Dicas disponiveis.

Ainda não resolveu?

Ver a solução agora pode reduzir o aprendizado. Vale a pena tentar mais um pouco.

Abrir a solucao de referencia

Sem JavaScript, a solucao de referencia aparece inline em vez de um dialogo.

Solução

Complexidade final

Tempo: O(n)

Espaço: O(1)

Solução 1 - Recursao com incluir ou pular antes de memoization

Boa versão inicial para deixar a decisão central bem explicita.

export function rob(nums: number[]): number {
  function solve(index: number): number {
    if (index >= nums.length) {
      return 0
    }

    return Math.max(solve(index + 1), nums[index] + solve(index + 2))
  }

  return solve(0)
}

Funciona, mas desperdica trabalho repetindo os mesmos indices.

Solução 2 - Programacao dinamica com estado reduzido em espaco O(1)

Agora a mesma ideia roda de forma linear.

export function rob(nums: number[]): number {
  let twoHousesBefore = 0
  let oneHouseBefore = 0

  for (const value of nums) {
    const current = Math.max(oneHouseBefore, twoHousesBefore + value)
    twoHousesBefore = oneHouseBefore
    oneHouseBefore = current
  }

  return oneHouseBefore
}

O ponto forte aqui e enxergar a escolha incluir-ou-não como estado.

O que dizer na entrevista

Uma explicação curta e boa seria:

Para cada casa, eu escolho entre pular e manter a melhor resposta anterior, ou roubar e somar com a melhor resposta de duas casas atras. Isso gera a recorrencia max(best(i - 1), best(i - 2) + nums[i]). Como só dependo dos dois estados anteriores, posso reduzir o espaco para O(1).

Isso mostra que você:

  • viu a decisão certa
  • evitou cair em guloso local
  • conectou a recorrencia a uma otimização de espaco

Quando esse padrão aparece de novo

Esse padrão reaparece quando você precisa:

  • escolher entre incluir ou excluir um item
  • lidar com restrição de adjacencia
  • montar programacao dinamica linear com dois estados
  • transformar uma recursão de decisão em iteração

O ponto não e decorar esse problema.

O ponto e aprender a modelar escolha com dependência curta.

Proximo desafio Busca em array rotacionado Desafio anterior Implementar fila usando pilhas

Desafios relacionados

Artigos relacionados