Aqui está uma O(N)
solução simples que usa O(N)
espaço. Estou assumindo que estamos restringindo a lista de entrada a números não negativos e que queremos encontrar o primeiro número não negativo que não está na lista.
- Encontre o comprimento da lista; vamos dizer que é
N
.
- Aloque uma matriz de
N
booleanos, inicializada para todos false
.
- Para cada número
X
na lista, se X
for menor que N
, defina o X'th
elemento da matriz como true
.
- Faça a varredura da matriz começando pelo índice
0
, procurando o primeiro elemento que é false
. Se você encontrar o primeiro false
no índice I
, então I
é a resposta. Caso contrário (ou seja, quando todos os elementos estiverem true
), a resposta é N
.
Na prática, a "matriz de N
booleanos" provavelmente seria codificada como um "bitmap" ou "bitset" representado como uma matriz byte
ou int
. Isso normalmente usa menos espaço (dependendo da linguagem de programação) e permite que a varredura da primeira false
seja feita mais rapidamente.
É assim / porque o algoritmo funciona.
Suponha que os N
números da lista não sejam distintos ou que um ou mais deles seja maior que N
. Isso significa que deve haver pelo menos um número no intervalo 0 .. N - 1
que não está na lista. Portanto, o problema de encontrar o menor número em falta devem, portanto, reduzir o problema de encontrar o número que falta menor menosN
. Isso significa que não precisamos controlar os números maiores ou iguais a N
... porque eles não serão a resposta.
A alternativa ao parágrafo anterior é que a lista é uma permutação dos números de 0 .. N - 1
. Nesse caso, a etapa 3 define todos os elementos da matriz como true
e a etapa 4 nos diz que o primeiro número "ausente" é N
.
A complexidade computacional do algoritmo é O(N)
com uma constante de proporcionalidade relativamente pequena. Ele faz duas passagens lineares pela lista, ou apenas uma passagem se o comprimento da lista for conhecido no início. Não há necessidade de representar o manter a lista inteira na memória, portanto, o uso de memória assintótica do algoritmo é exatamente o que é necessário para representar a matriz de booleanos; ou seja, O(N)
bits.
(Por outro lado, algoritmos que dependem de classificação ou particionamento na memória pressupõem que você pode representar a lista inteira na memória. Na forma em que a pergunta foi feita, isso exigiria O(N)
palavras de 64 bits.)
@Jorn comenta que as etapas 1 a 3 são uma variação da classificação por contagem. Em certo sentido, ele está certo, mas as diferenças são significativas:
- Uma classificação de contagem requer uma matriz de (pelo menos)
Xmax - Xmin
contadores onde Xmax
é o maior número da lista e Xmin
é o menor número da lista. Cada contador deve ser capaz de representar N estados; isto é, assumindo uma representação binária, ela deve ter um tipo inteiro (pelo menos) ceiling(log2(N))
bits.
- Para determinar o tamanho da matriz, uma classificação de contagem precisa fazer uma passagem inicial pela lista para determinar
Xmax
e Xmin
.
- O requisito mínimo de espaço no pior caso é, portanto,
ceiling(log2(N)) * (Xmax - Xmin)
bits.
Em contraste, o algoritmo apresentado acima simplesmente requer N
bits nos piores e melhores casos.
No entanto, essa análise leva à intuição de que se o algoritmo fizesse uma passagem inicial pela lista procurando um zero (e contando os elementos da lista, se necessário), daria uma resposta mais rápida usando nenhum espaço se encontrasse o zero. Definitivamente, vale a pena fazer isso se houver uma alta probabilidade de encontrar pelo menos um zero na lista. E essa passagem extra não altera a complexidade geral.
EDIT: Eu mudei a descrição do algoritmo para usar "array de booleanos" desde que as pessoas aparentemente acharam minha descrição original usando bits e bitmaps para ser confusa.