rafaelr.dev

Como Encontrar Duplicados em uma Lista?

🇧🇷 PT🇺🇸 EN

2025-04-20

4 min read

Raciocínio lógico é uma habilidade fundamental para qualquer programador. Para continuar aprimorando a minha, utilizo frequentemente plataformas como HackerRank e LeetCode. Elas oferecem desafios de programação para todos os níveis, permitindo melhorar a capacidade de resolução de problemas enquanto praticamos estruturas de dados como ferramentas para soluções eficazes.

Problema

Durante um dos exercícios, encontrei um problema interessante que vale a pena compartilhar. À primeira vista, ele parece bastante simples:

Dado um vetor de inteiros nums contendo n + 1 inteiros, onde cada inteiro está no intervalo [1, n], retorne o número duplicado.

  • Exatamente um inteiro aparece mais de uma vez.
  • Você não pode modificar o vetor (nums) e deve usar apenas espaço extra constante.

Restrições

A garantia de que existe um número duplicado vem do Princípio da Casa dos Pombos — um conceito fundamental da matemática. Vale a pena conferir sua prova, pois ela se relaciona elegantemente com vários cenários do mundo real.

O que torna esse problema realmente desafiador é a restrição adicional:

Você deve resolver o problema sem modificar o vetor de entrada e utilizando apenas espaço extra constante.

Essa restrição impede o uso de mapas de hash ou conjuntos, tornando abordagens tradicionais inviáveis.

Solução Ingênua (Ignorando Restrições)

Este é um problema clássico com uma solução bem conhecida que ignora as restrições inicialmente, o que ajuda a construir entendimento.

Vamos começar com uma abordagem direta:
Se ordenarmos o vetor, quaisquer elementos duplicados ficarão adjacentes.

Etapas:

  1. Ordenar o vetor nums.
  2. Iterar sobre o vetor.
  3. Se algum elemento for igual ao anterior, retorná-lo.

Código em Python

def findDuplicate(nums):
    nums.sort()
    for i in range(1, len(nums)):
        if nums[i] == nums[i - 1]:
            return nums[i]

Complexidade de Tempo e Espaço

Embora eficaz, essa solução viola as restrições por modificar o vetor e utilizar mais do que espaço constante.

Solução Ideal: Detecção de Ciclo

Uma abordagem mais sofisticada é modelar o vetor como uma lista encadeada, onde cada índice aponta para o valor nesse índice. Devido ao valor duplicado, um ciclo deve existir. O problema se reduz à detecção de ciclo em listas encadeadas — especificamente, encontrar o início do ciclo.

Esse conceito é ilustrado abaixo:

Cycle Intro

Trata-se essencialmente do problema Linked List Cycle II.

Vamos ampliar o vetor para melhor visualização:

Cycle Concept

Algoritmo da Tartaruga e da Lebre de Floyd

Este algoritmo usa dois ponteiros que se movem em velocidades diferentes:

Fase 1: Encontrando a Interseção

Observação: o ponto de interseção nem sempre é o início do ciclo.

Phase 1

Fase 2: Encontrando a Entrada do Ciclo

Reinicie o ponteiro tortoise para o início (nums[0]) e mova ambos com a mesma velocidade. Eles se encontrarão na entrada do ciclo, que é o número duplicado.

Phase 2

Código em Python

def findDuplicate(nums):
    # Fase 1: Encontrar o ponto de interseção
    tortoise = hare = nums[0]
    while True:
        tortoise = nums[tortoise]
        hare = nums[nums[hare]]
        if tortoise == hare:
            break

    # Fase 2: Encontrar a entrada do ciclo
    tortoise = nums[0]
    while tortoise != hare:
        tortoise = nums[tortoise]
        hare = nums[hare]

    return hare

Complexidade de Tempo e Espaço

Esta solução é elegante e ótima, transformando um problema aparentemente de array em uma detecção de ciclo com ponteiros.

Dica: Se estiver com dificuldades para visualizar o processo, imprima no console os valores dos ponteiros tortoise e hare para acompanhar seus caminhos.

Referências

Rafael Ribeiro

Rafael Ribeiro

Software Engineer