Uma pergunta de entrevista interessante que um colega meu usa:
Suponha que você receba uma lista muito longa e não classificada de inteiros de 64 bits não assinados. Como você encontraria o menor inteiro não negativo que não ocorre na lista?
SEGUIMENTO: Agora que a solução óbvia por classificação foi proposta, você pode fazer isso mais rápido do que O (n log n)?
SEGUIMENTO: seu algoritmo deve ser executado em um computador com, digamos, 1 GB de memória
ESCLARECIMENTO: A lista está na RAM, embora possa consumir uma grande quantidade dela. Você recebe o tamanho da lista, digamos N, com antecedência.
Respostas:
Se a estrutura de dados pode sofrer mutação no local e suportar acesso aleatório, você pode fazer isso em tempo O (N) e espaço adicional O (1). Basta percorrer a matriz sequencialmente e, para cada índice, escreva o valor do índice no índice especificado por valor, colocando recursivamente qualquer valor naquele local em seu lugar e jogando fora os valores> N. Em seguida, percorra novamente a matriz procurando o local onde o valor não corresponde ao índice - esse é o menor valor que não está na matriz. Isso resulta em no máximo 3N comparações e usa apenas alguns valores de espaço temporário.
fonte
Aqui está uma
O(N)
solução simples que usaO(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.N
.N
booleanos, inicializada para todosfalse
.X
na lista, seX
for menor queN
, defina oX'th
elemento da matriz comotrue
.0
, procurando o primeiro elemento que éfalse
. Se você encontrar o primeirofalse
no índiceI
, entãoI
é a resposta. Caso contrário (ou seja, quando todos os elementos estiveremtrue
), a resposta éN
.Na prática, a "matriz de
N
booleanos" provavelmente seria codificada como um "bitmap" ou "bitset" representado como uma matrizbyte
ouint
. Isso normalmente usa menos espaço (dependendo da linguagem de programação) e permite que a varredura da primeirafalse
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 queN
. Isso significa que deve haver pelo menos um número no intervalo0 .. 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 aN
... 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 comotrue
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:
Xmax - Xmin
contadores ondeXmax
é o maior número da lista eXmin
é 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.Xmax
eXmin
.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.
fonte
bool[]
ou por um bitmap é irrelevante para a solução geral.Como o OP agora especificou que a lista original é mantida na RAM e que o computador tem apenas, digamos, 1 GB de memória, vou arriscar e prever que a resposta é zero.
1 GB de RAM significa que a lista pode ter no máximo 134.217.728 números. Mas existem 2 64 = 18.446.744.073.709.551.616 números possíveis. Portanto, a probabilidade de que zero esteja na lista é 1 em 137.438.953.472.
Em contraste, minhas chances de ser atingido por um raio este ano são de 1 em 700.000. E minhas chances de ser atingido por um meteorito são de cerca de 1 em 10 trilhões. Portanto, tenho cerca de dez vezes mais probabilidade de ser escrito em um jornal científico devido à minha morte prematura por um objeto celestial do que a resposta não ser zero.
fonte
Conforme indicado em outras respostas, você pode fazer uma classificação e, em seguida, simplesmente examinar até encontrar uma lacuna.
Você pode melhorar a complexidade algorítmica para O (N) e manter o espaço O (N) usando um QuickSort modificado, onde você elimina partições que não são candidatas em potencial para conter a lacuna.
Isso economiza um grande número de cálculos.
fonte
Como todos os números têm 64 bits, podemos usar a ordenação de raiz neles, que é O (n). Classifique-os e examine-os até encontrar o que procura.
se o menor número for zero, avance até encontrar uma lacuna. Se o menor número não for zero, a resposta será zero.
fonte
Para ilustrar uma das armadilhas do
O(N)
pensamento, aqui está umO(N)
algoritmo que usaO(1)
espaço.fonte
Para um método eficiente de espaço e todos os valores são distintos, você pode fazê-lo no espaço
O( k )
e no tempoO( k*log(N)*N )
. É eficiente em termos de espaço e não há movimentação de dados e todas as operações são elementares (adição e subtração).U = N; L=0
k
regiões. Como isso:0->(1/k)*(U-L) + L
,0->(2/k)*(U-L) + L
,0->(3/k)*(U-L) + L
...0->(U-L) + L
count{i}
) existem em cada região. (N*k
passos)h
) que não está completa. Isso significacount{h} < upper_limit{h}
. (k
passos)h - count{h-1} = 1
você tem sua respostaU = count{h}; L = count{h-1}
isso pode ser melhorado usando hashing (obrigado por Nic esta ideia).
k
regiões. Como isso:L + (i/k)->L + (i+1/k)*(U-L)
inc count{j}
usandoj = (number - L)/k
(if L < number < U)
h
) que não contém k elementoscount{h} = 1
h é sua respostaU = maximum value in region h
L = minimum value in region h
Isso vai entrar em ação
O(log(N)*N)
.fonte
U-L < k
Eu apenas classificaria e, em seguida, percorreria a sequência até encontrar uma lacuna (incluindo a lacuna no início entre zero e o primeiro número).
Em termos de algoritmo, algo como isto faria:
Obviamente, se você tiver muito mais memória do que a CPU grunhida, poderá criar uma máscara de bits de todos os valores possíveis de 64 bits e apenas definir os bits para cada número da lista. Em seguida, procure o primeiro bit 0 nessa máscara de bits. Isso a transforma em uma operação O (n) em termos de tempo, mas muito cara em termos de requisitos de memória :-)
Duvido que você possa melhorar em O (n), pois não vejo uma maneira de fazer isso que não envolva olhar para cada número pelo menos uma vez.
O algoritmo para isso seria ao longo das linhas de:
fonte
Classifique a lista, observe o primeiro e o segundo elementos e comece a subir até que haja uma lacuna.
fonte
Você pode fazer isso em tempo O (n) e espaço adicional O (1), embora o fator oculto seja muito grande. Esta não é uma maneira prática de resolver o problema, mas pode ser interessante mesmo assim.
Para cada inteiro não assinado de 64 bits (em ordem crescente), itere na lista até encontrar o inteiro de destino ou chegar ao final da lista. Se você chegar ao final da lista, o inteiro de destino é o menor inteiro que não está na lista. Se você chegar ao final dos inteiros de 64 bits, todos os inteiros de 64 bits estarão na lista.
Aqui está como uma função Python:
Esta função é deliberadamente ineficiente para mantê-lo O (n). Observe especialmente que a função continua verificando os inteiros alvo mesmo depois que a resposta for encontrada. Se a função retornasse assim que a resposta fosse encontrada, o número de vezes que o loop externo funcionava seria limitado pelo tamanho da resposta, que é limitado por n. Essa mudança tornaria o tempo de execução O (n ^ 2), embora fosse muito mais rápido.
fonte
Agradeço a egon, swilden e Stephen C pela minha inspiração. Primeiro, sabemos os limites do valor da meta porque ele não pode ser maior do que o tamanho da lista. Além disso, uma lista de 1 GB pode conter no máximo 134217728 (128 * 2 ^ 20) inteiros de 64 bits.
Parte de hash
Proponho usar hashing para reduzir drasticamente nosso espaço de busca. Primeiro, faça a raiz quadrada do tamanho da lista. Para uma lista de 1 GB, isso é N = 11.586. Configure uma matriz de inteiros de tamanho N. Repita a lista e tire a raiz quadrada * de cada número que encontrar como seu hash. Em sua tabela de hash, incremente o contador desse hash. Em seguida, itere em sua tabela de hash. O primeiro intervalo que você descobrir que não é igual ao tamanho máximo define seu novo espaço de pesquisa.
Parte do bitmap
Agora configure um bitmap regular igual ao tamanho do seu novo espaço de pesquisa e, novamente, itere pela lista de origem, preenchendo o bitmap à medida que encontra cada número em seu espaço de pesquisa. Quando terminar, o primeiro bit não definido em seu bitmap lhe dará sua resposta.
Isso será concluído em tempo O (n) e espaço O (sqrt (n)).
(* Você poderia usar algo como deslocamento de bits para fazer isso com muito mais eficiência e apenas variar o número e o tamanho dos intervalos de acordo.)
fonte
Bem, se houver apenas um número ausente em uma lista de números, a maneira mais fácil de encontrar o número ausente é somar a série e subtrair cada valor na lista. O valor final é o número que falta.
fonte
fonte
Poderíamos usar uma tabela hash para armazenar os números. Assim que todos os números estiverem prontos, execute um contador de 0 até encontrar o mais baixo. Um hash razoavelmente bom faz o hash e o armazenamento em tempo constante e recupera em tempo constante.
O pior caso se houver
n
elementos no array, e forem{0, 1, ... n-1}
, nesse caso, a resposta será obtida emn
, ainda mantendo-oO(n)
.fonte
Aqui está minha resposta escrita em Java:
Ideia Básica: 1- Faça um loop pela matriz jogando fora os números positivos, zeros e negativos duplicados enquanto soma o resto, obtendo também o número positivo máximo, e mantenha os números positivos únicos em um Mapa.
2- Calcule a soma como max * (max + 1) / 2.
3- Encontre a diferença entre as somas calculadas nas etapas 1 e 2
4- Faça um loop novamente de 1 ao mínimo de [soma a diferença, máx.] E retorne o primeiro número que não está no mapa preenchido na etapa 1.
fonte
Como Stephen C observou com inteligência, a resposta deve ser um número menor do que o comprimento do array. Eu então encontraria a resposta por pesquisa binária. Isso otimiza o pior caso (para que o entrevistador não possa pegá-lo em um cenário patológico 'e se'). Em uma entrevista, indique que você está fazendo isso para otimizar para o pior caso.
A maneira de usar a pesquisa binária é subtrair o número que você está procurando de cada elemento da matriz e verificar se há resultados negativos.
fonte
Eu gosto da abordagem "acho que zero". Se os números forem aleatórios, zero é altamente provável. Se o "examinador" definir uma lista não aleatória, adicione uma e tente novamente:
O pior caso é n * N com n = N, mas na prática n é altamente provável que seja um número pequeno (por exemplo, 1)
fonte
Não tenho certeza se entendi a pergunta. Mas se para a lista 1,2,3,5,6 e o número ausente for 4, então o número ausente pode ser encontrado em O (n) por: (n + 2) (n + 1) / 2- (n + 1) n / 2
EDIT: desculpe, acho que estava pensando muito rápido na noite passada. De qualquer forma, a segunda parte deve ser substituída por soma (lista), que é de onde vem O (n). A fórmula revela a ideia por trás disso: para n inteiros sequenciais, a soma deve ser (n + 1) * n / 2. Se houver um número ausente, a soma seria igual à soma de (n + 1) inteiros sequenciais menos o número ausente.
Obrigado por apontar o fato de que eu estava colocando algumas peças intermediárias em minha mente.
fonte
Muito bem Formigas Aasma! Eu pensei sobre a resposta por cerca de 15 minutos e, de forma independente, encontrei uma resposta em uma linha de pensamento semelhante à sua:
m representa "a saída máxima possível atual, dado o que sei sobre as primeiras entradas i e assumindo nada mais sobre os valores até a entrada em m-1".
Este valor de m será retornado apenas se (a [i], ..., a [m-1]) for uma permutação dos valores (i, ..., m-1). Assim, se a [i]> = m ou se a [i] <i ou se a [i] == a [a [i]], sabemos que m é a saída errada e deve ser pelo menos um elemento a menos. Portanto, decrementando me trocando a [i] com a [m] podemos recursar.
Se isso não for verdade, mas a [i]> i, então sabendo que a [i]! = A [a [i]], sabemos que trocar a [i] por a [a [i]] aumentará o número de elementos em seu próprio lugar.
Caso contrário, a [i] deve ser igual a i, caso em que podemos incrementar i sabendo que todos os valores de até e incluindo este índice são iguais ao seu índice.
A prova de que isso não pode entrar em um loop infinito é deixada como um exercício para o leitor. :)
fonte
O fragmento Dafny da resposta das formigas mostra por que o algoritmo local pode falhar. A
requires
pré-condição descreve que os valores de cada item não devem ultrapassar os limites da matriz.Cole o código no validador com e sem a
forall ...
cláusula para ver o erro de verificação. O segundo erro é o resultado do verificador não ser capaz de estabelecer uma condição de término para o loop da passagem 1. Provar isso é deixado para alguém que entende melhor a ferramenta.fonte
Aqui está uma resposta em Java que não modifica a entrada e usa o tempo O (N) e N bits mais uma pequena sobrecarga constante de memória (onde N é o tamanho da lista):
fonte
Obteve 100% para a solução acima.
fonte
1) Filtro negativo e zero
2) Classificar / distinguir
3) Visit array
Complexidade : O (N) ou O (N * log (N))
usando Java8
fonte
Um unordered_set pode ser usado para armazenar todos os números positivos, e então podemos iterar de 1 até o comprimento de unordered_set e ver o primeiro número que não ocorre.
fonte
Solução através de javascript básico
Espero que isso ajude alguém.
fonte
Com python não é o mais eficiente, mas correto
fonte
fonte
isso pode ajudar:
fonte