Como comparar duas soluções para o mesmo problema
Compare soluções corretas por trabalho, memória, clareza, efeitos colaterais e contexto e registre uma decisão técnica revisável.

A melhor solução só existe dentro de um contexto
Dois algoritmos podem devolver a resposta correta e ainda assim serem escolhas diferentes. Um pode usar menos memória, outro executar menos comparações. Um pode ser direto para uma coleção pequena, enquanto o outro se torna mais adequado quando a entrada cresce. Um terceiro pode ser rápido, mas difícil de alterar com segurança.
Por isso, perguntar “qual código é melhor?” sem declarar o problema, as restrições e os critérios produz apenas preferência pessoal. Uma comparação técnica precisa responder:
- as duas soluções obedecem ao mesmo contrato?
- funcionam para os mesmos casos válidos e inválidos?
- quais recursos cada uma consome?
- quais efeitos colaterais produzem?
- qual delas torna a próxima mudança menos arriscada?
- que característica importa mais neste contexto?
Nesta aula, vamos construir uma decisão verificável. A próxima aula aprofundará como o custo cresce com a entrada; aqui usaremos contagens simples, medições cuidadosas e critérios de engenharia suficientes para escolher sem antecipar toda a análise formal.
Defina uma base comum antes de comparar
Considere o problema: informar se uma coleção contém algum valor repetido. O contrato será:
- receber uma coleção de valores comparáveis pela regra de igualdade adotada;
- devolver
verdadeiroquando qualquer valor aparecer pelo menos duas vezes; - devolver
falsopara coleção vazia, coleção com um item ou coleção sem repetições; - não modificar a coleção recebida;
- tratar entradas fora do domínio conforme a validação já definida pelo sistema.
Essa base evita comparar soluções para problemas discretamente diferentes. Se uma versão considera "A" e "a" iguais e outra não, a divergência é de contrato. Se uma ordena a coleção original e outra preserva sua ordem, existe diferença observável mesmo que o booleano final coincida.
Use a mesma suíte para ambas, incluindo casos extremos e entradas inválidas:
| Caso | Entrada | Esperado |
|---|---|---|
| C1 | [] | falso |
| C2 | [4] | falso |
| C3 | [4, 7, 2] | falso |
| C4 | [4, 7, 4] | verdadeiro |
| C5 | [4, 4, 7] | verdadeiro |
| C6 | coleção longa sem repetição | falso |
Só avance para velocidade, memória ou legibilidade depois que as alternativas passarem pelo mesmo contrato.
Solução A: comparar cada par necessário
A primeira estratégia compara cada elemento com os que vêm depois dele:
função tem_repetido_por_pares(valores):
para i de 0 até tamanho(valores) - 2:
para j de i + 1 até tamanho(valores) - 1:
se valores[i] = valores[j]:
retorne verdadeiro
retorne falso
O início de j em i + 1 evita comparar um elemento consigo mesmo e impede repetir o mesmo par em ordem inversa. A solução encerra assim que encontra uma igualdade.
Pontos favoráveis:
- usa poucas estruturas auxiliares;
- expressa diretamente a pergunta “algum par é igual?”;
- funciona mesmo quando os valores não podem ser usados como chaves de um conjunto;
- pode ser suficiente para entradas pequenas e limitadas.
Custos e riscos:
- quando não há repetição, examina todos os pares;
- o número de comparações cresce rapidamente com a quantidade de elementos;
- dois laços e dois índices ampliam a superfície para erros de limite;
- uma regra de igualdade cara será executada muitas vezes.
A familiaridade com estruturas de repetição aninhadas ajuda a prever a ordem dessas comparações.
Solução B: registrar valores já vistos
A segunda estratégia mantém um conjunto auxiliar:
função tem_repetido_com_conjunto(valores):
vistos ← conjunto vazio
para cada valor em valores:
se valor pertence a vistos:
retorne verdadeiro
adicione valor a vistos
retorne falso
Cada valor pergunta se já apareceu. Se não apareceu, passa a fazer parte do histórico. A solução também encerra no primeiro repetido.
Pontos favoráveis:
- percorre a coleção uma vez;
- separa claramente o estado
vistosda entrada original; - evita recomparar todos os pares;
- costuma se adaptar melhor a entradas maiores.
Custos e restrições:
- o conjunto ocupa memória adicional que pode crescer com a entrada;
- os valores precisam ser compatíveis com a estrutura e sua regra de igualdade;
- o custo de consulta depende da implementação do conjunto;
- a solução pode introduzir um conceito desnecessário para uma entrada muito pequena e rigidamente limitada.
Uma estrutura pronta não torna a escolha automaticamente superior. Ela troca trabalho repetido por armazenamento e por pressupostos sobre os dados.
Conte operações antes de cronometrar
Para cinco valores distintos, a solução por pares realiza no pior percurso:
4 + 3 + 2 + 1 = 10 comparações
Com n elementos sem repetição, o total de pares é n × (n - 1) ÷ 2. A solução com conjunto faz até uma consulta e uma inserção por valor: cinco consultas e cinco inserções no exemplo, considerando as operações oferecidas pela estrutura.
Essa contagem não encerra a análise. Uma consulta ao conjunto não é “gratuita”, e diferentes tipos de valor podem mudar seu custo. Ainda assim, o modelo revela por que as estratégias tendem a se comportar de maneira diferente quando a entrada aumenta.
Também observe a distribuição dos dados. Se a repetição estiver nas primeiras posições, ambas podem retornar cedo. Se ela não existir, as duas percorrem seus caminhos completos. Uma comparação justa usa tamanhos e distribuições que representam o uso real.
Correção é eliminatória, não uma pontuação
Não dê nota alta de desempenho para compensar uma resposta errada. Correção, preservação do contrato e restrições obrigatórias funcionam como filtros:
atende ao contrato? não → eliminar
passa pela suíte comum? não → eliminar
respeita limites obrigatórios? não → eliminar
continua candidata? sim → comparar qualidades
Se a solução B não aceita o tipo de valor exigido, ela não recebe “menos um ponto”; deixa de ser candidata naquele contexto. Se a solução A estoura o prazo máximo para a carga prevista, ocorre o mesmo.
Essa ordem protege a decisão contra uma planilha enganosa em que várias vantagens pequenas escondem uma falha essencial.
Compare tempo e memória como uma troca
Tempo e memória frequentemente apontam em direções diferentes:
| Critério | Comparação por pares | Conjunto de vistos |
|---|---|---|
| Trabalho sem repetição | compara todos os pares | consulta cada valor uma vez |
| Memória auxiliar | pequena e praticamente constante | cresce com os valores distintos |
| Retorno antecipado | ao encontrar um par igual | ao reencontrar um valor |
| Dependência estrutural | igualdade | conjunto, igualdade e representação compatível |
| Entrada original | preservada | preservada |
Em um dispositivo com memória extremamente restrita e coleções de no máximo cinco itens, a solução A pode ser a escolha coerente. Em um serviço que recebe milhares de identificadores e possui memória disponível, a solução B tende a evitar muito trabalho repetido.
Não há contradição: o contexto alterou o peso dos critérios.
Verifique efeitos colaterais e pressupostos ocultos
Uma terceira ideia seria ordenar a coleção e procurar vizinhos iguais. Ela pode parecer atraente, mas abre perguntas novas:
- todos os valores possuem uma ordem definida?
- ordenar modifica a entrada ou trabalha sobre uma cópia?
- criar a cópia consome memória aceitável?
- a ordem original tem significado para quem chamou a função?
- a função de comparação representa a mesma igualdade usada para detectar duplicados?
Esse exemplo mostra por que a saída principal não é a única observação. Mutação, exceções, dependências e pré-condições fazem parte do comportamento da solução.
Registre explicitamente:
entrada modificada: não
estrutura auxiliar: nenhuma / conjunto
pressuposto sobre valores: comparáveis por igualdade / aceitos pelo conjunto
falhas possíveis: limites de índice / incompatibilidade com a estrutura
Avalie clareza com tarefas concretas
“Mais legível” não deve significar apenas “mais parecido com o código que eu costumo escrever”. Teste a clareza por ações que outra pessoa precisa realizar:
- explicar a ideia sem executar cada linha;
- localizar onde a repetição é detectada;
- alterar a regra de igualdade;
- adicionar um teste sem conhecer detalhes irrelevantes;
- prever quais dados auxiliares existem;
- revisar os limites dos laços;
- descobrir por que a decisão foi tomada meses depois.
A orientação de revisão de código do Google recomenda basear decisões em fatos e princípios de engenharia, preservando compreensibilidade e manutenibilidade em vez de buscar uma perfeição abstrata. Nomes, fluxo simples e comentários que explicam o porquê ajudam mais que comentários que traduzem instruções confusas.
Para entradas pequenas, a solução por pares pode ser entendida imediatamente por quem domina índices. Para uma equipe familiarizada com conjuntos, a segunda expressa com mais proximidade a ideia “já vi este valor?”. Clareza também depende do repertório legítimo do projeto, mas não deve congelar a equipe em uma solução inadequada.
Considere manutenção e mudança provável
Imagine três mudanças futuras:
- devolver qual valor se repetiu;
- contar quantos valores distintos se repetem;
- processar dados que chegam progressivamente.
O conjunto de vistos oferece uma base natural para as três, embora cada mudança ainda exija contrato e testes próprios. A solução por pares pode continuar válida, mas tende a repetir comparações ou acumular condições.
Agora imagine outra mudança: aceitar objetos que não possuem representação compatível com o conjunto, mas oferecem uma função de igualdade específica. Nesse cenário, a solução A pode exigir menos adaptação.
Manutenibilidade não é adivinhar todos os futuros. É observar a direção provável do produto, a estabilidade dos requisitos e o custo de mudar sem introduzir defeitos. A divisão do algoritmo em partes também ajuda quando a igualdade ou a estratégia de armazenamento precisa variar isoladamente.
Meça quando a decisão depender de desempenho
Depois de formular uma hipótese, faça uma medição controlada. Use:
- a mesma linguagem, versão e ambiente;
- implementações equivalentes em correção;
- entradas iguais para cada execução;
- tamanhos pequenos, típicos e grandes;
- coleções sem repetição, com repetição precoce e tardia;
- várias repetições, não uma execução isolada;
- tempo e memória quando ambos importarem;
- preparação dos dados separada do trecho medido.
Ferramentas como timeit, documentada pelo Python, existem porque medir pequenos trechos contém armadilhas. O sistema operacional, aquecimento, coleta de memória e trabalho de preparação podem distorcer resultados. Uma diferença mínima em um microteste também pode ser irrelevante para o produto completo.
Meça com dados representativos e registre o ambiente. Se a restrição for “responder em até 100 ms para 50 mil itens”, compare contra essa exigência, não contra o desejo genérico de ser rápido.
Use uma matriz sem fabricar precisão
Uma matriz organiza evidências, mas os pesos precisam vir do contexto. Considere duas situações:
| Critério | Dispositivo: até 5 itens | Serviço: até 100 mil itens |
|---|---|---|
| Correção | obrigatória | obrigatória |
| Memória auxiliar | peso alto | peso médio |
| Trabalho quando a entrada cresce | peso baixo | peso alto |
| Compatibilidade dos valores | peso alto | peso alto |
| Simplicidade para a equipe | peso médio | peso médio |
No dispositivo, A pode vencer porque a quantidade é pequena, o limite é garantido e memória é crítica. No serviço, B pode vencer porque evitar pares domina a decisão e o conjunto é compatível com os identificadores.
Evite somar notas como se 4,2 fosse uma verdade científica. Escalas ajudam a ordenar uma conversa, mas não substituem restrições, medições nem justificativas. Se uma pequena mudança de peso inverte a escolha, declare que a decisão é sensível e planeje reavaliá-la.
Registre uma decisão que possa ser revisada
Uma decisão técnica curta pode seguir este modelo:
problema: detectar valores repetidos sem alterar a entrada
alternativas: comparar pares; registrar valores vistos
contexto: até 100 mil identificadores compatíveis com conjunto
critérios obrigatórios: correção, preservação da entrada, limite de tempo
evidências: mesma suíte; contagem de operações; medição no ambiente-alvo
decisão: conjunto de vistos
troca aceita: memória auxiliar para reduzir comparações repetidas
gatilho de revisão: mudança no tipo dos valores ou no limite de memória
O registro preserva o raciocínio que o código sozinho não mostra. Ele também impede que uma escolha contextual vire regra eterna: se o tamanho máximo, o hardware ou o tipo dos dados mudar, o gatilho indica que a comparação precisa ser refeita.
Evite atalhos que parecem critérios
Alguns argumentos são insuficientes quando aparecem sozinhos:
- “tem menos linhas”: concisão não garante clareza nem menor custo;
- “usa uma estrutura avançada”: sofisticação não prova adequação;
- “foi mais rápido uma vez”: uma execução não controla variação;
- “tem a melhor complexidade”: o modelo não remove constantes, memória, restrições ou efeitos;
- “é o padrão da internet”: falta o contexto que originou a recomendação;
- “sempre fizemos assim”: consistência importa, mas não justifica perpetuar um problema;
- “pode ser necessário no futuro”: otimização especulativa precisa de risco concreto.
Prefira uma frase verificável: “para coleções de até cinco itens, A passou pela suíte e pelo limite medido usando menos memória auxiliar” ou “para 100 mil identificadores, B cumpriu o prazo e A não cumpriu”.
Pratique com duas formas de encontrar interseção
Compare duas soluções para responder se duas coleções compartilham algum valor:
solução A: comparar cada item da primeira com cada item da segunda
solução B: registrar os itens da menor coleção e consultar os da maior
Siga esta sequência:
- escreva um único contrato, inclusive para vazios e tipos incompatíveis;
- crie uma suíte comum com ausência e presença de interseção;
- prove que nenhuma solução modifica as entradas;
- conte comparações, consultas e itens armazenados;
- avalie coleções de tamanhos muito diferentes;
- declare quais tipos podem participar do conjunto;
- escolha dois contextos com restrições opostas;
- produza uma decisão e um gatilho de revisão para cada contexto.
Se você não consegue explicar por que a vencedora muda, os critérios ainda estão vagos.
O que você deve guardar
Compare soluções em camadas. Primeiro fixe o contrato e use a mesma suíte. Elimine alternativas incorretas ou incompatíveis com restrições obrigatórias. Depois avalie trabalho, memória, efeitos colaterais, clareza, manutenção e capacidade de mudança.
Conte operações antes de medir e, quando o desempenho importar, use entradas representativas, repetições e ambiente controlado. Uma matriz organiza evidências, mas seus pesos vêm do uso real. A decisão final precisa registrar a troca aceita e o evento que exigirá nova avaliação.
O próximo passo é estudar a introdução à eficiência de algoritmos para descrever formalmente como trabalho e memória crescem com a entrada.
Referências
- MIT OpenCourseWare — 6.006 Introduction to Algorithms, Lecture 1. Acesso em 22 set. 2026.
- NIST — Dictionary of Algorithms and Data Structures. Acesso em 22 set. 2026.
- CSTA — Standards for CS Teachers. Acesso em 22 set. 2026.
- Google Engineering Practices — The Standard of Code Review. Acesso em 22 set. 2026.
- Google Engineering Practices — What to Look For in a Code Review. Acesso em 22 set. 2026.
- ISO — ISO/IEC 25010:2023 Product quality model. Acesso em 22 set. 2026.
- Python Documentation —
timeit: Measure execution time of small code snippets. Acesso em 22 set. 2026.