Pular para o conteudo principal

Troco com moedas

Como sair da tentativa cega de combinações e montar uma programação dinamica que procura o menor custo para cada valor até o alvo.

Dado um array coins com valores de moedas diferentes e um inteiro amount, retorne o menor número de moedas necessário para formar amount.

Se não for possível formar esse valor, retorne -1.

Você pode usar cada moeda quantas vezes quiser.

Exemplo:

Input:  coins = [1, 2, 5], amount = 11
Output: 3

Porque 11 = 5 + 5 + 1.

O que perceber antes de codar

Erros comuns

Passo 1 - Traduzir o que realmente esta sendo pedido

Esse problema parece de combinação.

Mas o enunciado não pergunta:

  • quantas formas existem

Ele pergunta:

  • qual e o menor número de moedas

Isso muda o tipo de estado.

Passo 2 - Escrever a versão inicial recursiva

A versão inicial natural e:

  • para o valor atual, tente cada moeda
  • resolva recursivamente o valor restante
  • pegue o menor resultado valido

Funciona.

Mas recalcula os mesmos restos várias vezes.

Passo 3 - Definir o estado certo

A pergunta boa vira:

  • qual e o menor número de moedas para montar cada valor de 0 até amount?

Isso sugere:

dp[value] = menor numero de moedas para formar value

Passo 4 - Montar a transição

Se você esta calculando value e tenta usar uma moeda coin, sobra:

value - coin

Se esse resto era possível, a nova resposta candidata vira:

dp[value - coin] + 1

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

Codigo inicial

export function coinChange(coins: number[], amount: number): number {
  // sua solução aqui
  return -1
}
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(amount * coins.length)

Espaço: O(amount)

Solução 1 - Recursao tentando todas as moedas antes de memoization

Boa versão inicial para deixar o subproblema visível.

export function coinChange(coins: number[], amount: number): number {
  function solve(remaining: number): number {
    if (remaining === 0) {
      return 0
    }

    if (remaining < 0) {
      return Infinity
    }

    let best = Infinity

    for (const coin of coins) {
      best = Math.min(best, solve(remaining - coin) + 1)
    }

    return best
  }

  const answer = solve(amount)

  return Number.isFinite(answer) ? answer : -1
}

O problema aqui não e a ideia. E o retrabalho.

Solução 2 - Programacao dinamica de baixo para cima com estado 1D

Agora cada valor e resolvido uma vez.

export function coinChange(coins: number[], amount: number): number {
  const dp = new Array(amount + 1).fill(Infinity)
  dp[0] = 0

  for (let value = 1; value <= amount; value += 1) {
    for (const coin of coins) {
      if (coin <= value) {
        dp[value] = Math.min(dp[value], dp[value - coin] + 1)
      }
    }
  }

  return Number.isFinite(dp[amount]) ? dp[amount] : -1
}

O raciocínio forte aqui e montar a resposta dos valores menores para os maiores.

O que dizer na entrevista

Uma explicação curta e boa seria:

O problema e de minimização, não de contagem. Entao eu defino dp[value] como o menor número de moedas para formar aquele valor. Para cada valor, testo cada moeda e tento melhorar a resposta usando dp[value - coin] + 1. No fim, se o estado final continuar impossivel, devolvo -1.

Isso mostra que você:

  • escolheu o estado certo
  • separou bem caso impossivel de caso valido
  • explicou a transição sem decorar formula

Quando esse padrão aparece de novo

Esse padrão reaparece quando você precisa:

  • minimizar custo ou quantidade
  • montar resposta em programacao dinamica 1D
  • reutilizar subproblemas por valor
  • distinguir “não existe resposta” de “resposta grande”

O ponto não e decorar coinChange.

O ponto e aprender a modelar minimização em programacao dinamica.

Proximo desafio Número de ilhas Desafio anterior Profundidade máxima de árvore binária

Desafios relacionados

Artigos relacionados