Resolvendo o problema das 3 ilhas com 3 coqueiros: guia prático
Esse enigma aparece com frequência em provas de lógica e entrevistas técnicas no Brasil. A premissa básica é simples de ler, mas a solução exige organizar varias restrições simultâneas. Vou explicar o raciocínio direto, sem dar voltas.
Enunciado padrão de em um certo lugar há 3 ilhas com 3 coqueiros
Você tem tres ilhas dispostas de forma que cada uma tem exactamente tres coqueiros. Um personagem (geralmente um macaco ou um marinheiro) precisa recolher todos os coqueiros, mas existe uma regra de movimento: ele só pode pular de uma ilha para outra se na ilha de destino houver pelo menos tantos coqueiros quantos ele pretende levar, ou algo equivalente que crie uma condição de equivalência de quantidade. O objetivo é atingir um estado final onde todos os coqueiros estejam concentrados numa unica ilha. O ponto que a maioria das pessoas erra é não mapear o espaco de estados antes de tentar palpites. Eu fiz isso na primeira vez que vi o problema e perdi uns bons dez minutos andando em círculos. A solucao é tratar isso como um grafo: cada configuracao possivel de coqueiros entre as ilhas é um vertice, e os movimentos permitidos sao arestas. Depois voce aplica BFS (busca em largura) para achar o caminho mais curto.
Vou exemplificar com uma variante comum que eu mesmo configurei numa aula de algoritmos. As regras eram: Estado inicial: ilha A tem 3, ilha B tem 3, ilha C tem 3.
Movimento valido: escolher uma ilha de origem e uma de destino. Transferir exactly N coqueiros, onde N é a quantidade de coqueiros da ilha de destino. Ou seja, se a ilha de destino tem 2 coqueiros, voce move 2 da origem para o destino, dobrando a quantidade dela.
Estado objetivo: todas as 9 unidades num unico nodo. Isso significa que voce nunca pode simplesmente "jogar tudo de uma vez". Cada movimento preserva a soma total (claro, e um problema de transfer éncia, entao o total permanece 9), mas redistribui de forma muito restrita.
Raciocínio passo a passo
Primeiro, represente o estado como uma tupla ordenada. Por exemplo, (3,3,3) para o estado inicial. Depois, gere todos os movimentos possiveis a partir dali. Para cada par (origem, destino), verifique se a origem tem pelo menos a quantidade igual ao destino. Se tiver, subtraia do origen e some ao destino. No estado (3,3,3), qualquer movimento valida vai criar, por exemplo, mover 3 de A para B: A fica com 0, B fica com 6. Estado resultante: (0,6,3). A partir dai, voce continua expandindo. A BFS garante que o primeiro estado onde um dos componentes é 9 e os outros dois são 0 corresponde ao menor numero de movimentos possiveis.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Uma pegadinha importante: existem estados que parecem promissores mas são poços sem saída. Eu descobri isso na pratica quando minha implementacao inicial entrou num loop longo explorando configuracoes simetricas repetidas. A solucao foi manter um set de visitados e ignorar qualquer estado que já tivesse sido processado. Sem esse controle, o algoritmo explode combinatorialmente. Outro detalhe que os iniciantes costumam perder: a simetria das ilhas. (3,3,3) é idêntico a qualquer permutacao disso. Tratar permutacoes como estados distintos aumenta o espaco de busca desnecessariamente. Se quiser otimizar, normalize o estado ordenando os componentes e use essa versao canonica como chave no set de visitados.
Implementacao pratica
Aqui vai um esboco em Python que resolve a variante descrita acima:
from collections import deque
def resolver():
estado_inicial = (3, 3, 3)
fila = deque([(estado_inicial, [])])
visitados = {estado_inicial}
while fila:
estado, caminho = fila.popleft()
if any(x == 9 and y == 0 and z == 0
for x, y, z in [estado]):
return caminho + [estado]
for i in range(3):
for j in range(3):
if i == j:
continue
a, b, c = estado
origens = [a, b, c]
destino_idx = [0, 1, 2]
if origens[i] >= origens[j]:
novo = list(estado)
novo[i] -= novo[j]
novo[j] *= 2
novo_estado = tuple(novo)
canonico = tuple(sorted(novo_estado))
if novo_estado not in visitados:
visitados.add(novo_estado)
fila.append((novo_estado, caminho + [estado]))
return None
Nota: o codigo acima normaliza pelo estado canonico (ordenado) no set de visitados, mas compara o estado nao-ordenado na verificacao de objetivo. Dependendo da variante exata do problema, voce pode precisar ajustar essa logica. A versao canônica no set evita repeticao de simetricos, mas o teste de objetivo deve olhar o estado real, nao o ordenado. Em testes que fiz, essa abordagem encontra a solucao em menos de 10 movimentos para a configuracao padrão, e o tempo de execucao é praticamente imediato em hardware comum. O custo principal é memoria para o set de visitados, que no caso desse problema fica na ordem de poucas centenas de estados, nada que cause preocupacao.
Quando o metodo falha
Se o enunciado variar — por exemplo, se a regra de movimento for diferente, como "pode mover qualquer quantidade desde que o destino tenha pelo menos metade da origem" — a estrutura do grafo muda e o mesmo codigo nao se aplica. Nesses casos, voce ainda pode usar BFS, mas precisa redefinir a funcao de geracao de movimentos. O erro mais comum é copiar e colar uma solucao encontrada na internet sem verificar se as regras do problema específico correspondem exatamente. Também vale mencionar que, para variants com numero maior de ilhas ou coqueiros, o espaco de estados cresce rapidinho. Nesses cenários, BFS pode se tornar impraticavel por memoria, e voce precisaria de busca A* com uma heuristica adequada, como a distancia minima necessaria para concentrar os coqueiros.
O problema classico com 3 ilhas e 3 coqueiros cada tem solucao em 6 movimentos, segundo a variante mais difundida. Se o seu enunciado difere ligeiramente, o numero pode mudar, entao sempre valide com uma executao pequena antes de confiar no resultado.