Eu tive uma entrevista com uma empresa de fundos de hedge em Nova York há alguns meses atrás e, infelizmente, não recebi a oferta de estágio como engenheiro de dados / software. (Eles também pediram que a solução estivesse em Python.)
Eu estraguei tudo sobre o primeiro problema de entrevista ...
Pergunta: Dada uma sequência de um milhão de números (Pi, por exemplo), escreva uma função / programa que retorne todos os números de 3 dígitos repetidos e o número de repetições maior que 1
Por exemplo: se a sequência fosse 123412345123456
:, a função / programa retornaria:
123 - 3 times
234 - 3 times
345 - 2 times
Eles não me deram a solução depois que eu falhei na entrevista, mas disseram que a complexidade do tempo para a solução era constante de 1000, pois todos os resultados possíveis estão entre:
000 -> 999
Agora que estou pensando nisso, não acho possível criar um algoritmo de tempo constante. É isso?
fonte
They did not give me the solution after I failed the interview, but they did tell me that the time complexity for the solution was constant of 1000 since all the possible outcomes are between: 000 --> 999
Este foi provavelmente o teste real. Para ver se você poderia provar a eles por que isso não é possível e mostrar a eles a complexidade mínima de tempo correta.Respostas:
Você saiu de ânimo leve, provavelmente não quer trabalhar para um fundo de hedge onde os quantos não entendem algoritmos básicos :-)
Não há como processar uma estrutura de dados de tamanho arbitrário
O(1)
se, como neste caso, você precisar visitar todos os elementos pelo menos uma vez. O melhor que você pode esperar éO(n)
, neste caso, onden
está o comprimento da string.Parece-me que você poderia tê-los impressionado de várias maneiras.
Primeiro, informando-lhes que é não possível fazê-lo em
O(1)
, a menos que você use o "suspeito" fundamentação apresentada acima.Segundo, mostrando suas habilidades de elite, fornecendo código Pythonic, como:
Isso gera:
embora você possa, é claro, modificar o formato de saída para o que desejar.
E, finalmente, dizendo a eles que quase certamente não há problema com uma
O(n)
solução, pois o código acima fornece resultados para uma sequência de um milhão de dígitos em menos de meio segundo. Também parece ter uma escala linear, pois uma sequência de 10.000.000 caracteres leva 3,5 segundos e uma sequência de 100.000.000 caracteres leva 36 segundos.E, se eles precisarem melhor do que isso, existem maneiras de paralelizar esse tipo de coisa que pode acelerar muito.
Evidentemente, não dentro de um único intérprete Python, devido ao GIL, mas você pode dividir a string em algo como (sobreposição indicada por
vv
é necessária para permitir o processamento adequado das áreas de fronteira):Você pode cultivá-las para separar os trabalhadores e combinar os resultados posteriormente.
A divisão da entrada e a combinação da saída provavelmente inundarão qualquer economia com pequenas cadeias (e possivelmente até milhões de dígitos), mas, para conjuntos de dados muito maiores, pode muito bem fazer a diferença. Meu mantra habitual de "medir, não acho" se aplica aqui, é claro.
Esse mantra também se aplica a outras possibilidades, como ignorar completamente o Python e usar uma linguagem diferente que pode ser mais rápida.
Por exemplo, o código C a seguir, executado no mesmo hardware que o código Python anterior, manipula cem milhões de dígitos em 0,6 segundos, aproximadamente a mesma quantidade de tempo que o código Python processou um milhão. Em outras palavras, muito mais rápido:
fonte
O(1)
én
é fixo ou delimitado.N
. Se você o dividir em duas partes na posiçãoN/2
, você ainda precisará levar em consideração o fato de que pode perder uma correspondência válida de três dígitos na "borda", no finalstring1
e no início destring2
. Portanto, você precisa verificar as correspondências entrestring1[N/2-2]
estring2[2]
(usando um índice baseado em zero), etc. Essa é a ideia.val -= 100 * (d[i]-'0');
para soltar o dígito inicial.val = 10*val + d[i+2]-'0'
para acumular um novo dígito menos significativo (seqüência normal-> análise de número inteiro).val % 100
é possivelmente não horrível, mas apenas se100
for uma constante em tempo de compilação, para que ela não use uma divisão HW real.Tempo constante não é possível. Todos os 1 milhão de dígitos precisam ser visualizados pelo menos uma vez, para que seja uma complexidade de tempo de O (n), em que n = 1 milhão neste caso.
Para uma solução O (n) simples, crie uma matriz de tamanho 1000 que represente o número de ocorrências de cada número possível de 3 dígitos. Avance 1 dígito por vez, primeiro índice == 0, último índice == 999997 e incremente a matriz [número de 3 dígitos] para criar um histograma (contagem de ocorrências para cada número possível de 3 dígitos). Em seguida, imprima o conteúdo da matriz com contagens> 1.
fonte
x-'0'
padrão não é válido no Python, é um C-ism (onde os caracteres são inteiros).Um milhão é pequeno para a resposta que dou abaixo. Esperando apenas que você precise executar a solução na entrevista, sem uma pausa, o seguinte funciona em menos de dois segundos e fornece o resultado necessário:
Esperamos que o entrevistador esteja procurando o uso das coleções de bibliotecas padrão.
Versão de execução paralela
Eu escrevi um post sobre isso com mais explicações.
fonte
O(1)
.A solução O (n) simples seria contar cada número de 3 dígitos:
Isso pesquisaria todos os 1 milhão de dígitos 1000 vezes.
Atravessando os dígitos apenas uma vez:
O tempo mostra que a iteração apenas uma vez no índice é duas vezes mais rápida que a utilização
count
.fonte
text.count()
?text.count
é feito em uma linguagem compilada de alta velocidade (por exemplo, C), em oposição a um loop interpretado no nível python lento, sim, há um desconto.count
está incorreta, pois não conta padrões sobrepostos. Note que'111'.count('11') == 1
quando esperamos que seja2
.O(n)
solução simples " está na verdadeO(10**d * n)
comd
o número de dígitos pesquisados en
o comprimento total da string. O segundo é oO(n)
tempo e oO(10**d + n)
espaço.Aqui está uma implementação NumPy do algoritmo "consenso" O (n): percorra todos os trigêmeos e bin à medida que avança. O binning é feito ao encontrar, digamos "385", adicionando um ao bin [3, 8, 5], que é uma operação O (1). As caixas são organizadas em um
10x10x10
cubo. Como o binning é totalmente vetorizado, não há loop no código.Sem surpresa, o NumPy é um pouco mais rápido que a solução Python pura do @ Daniel em grandes conjuntos de dados. Saída de amostra:
fonte
ndarray
s, o tipo numpy central, trata-se de armazenamento, manipulação e indexação eficiente de matrizes multidimensionais de números. Às vezes, você pode cortar alguns% achatando, mas nesse caso, fazer 100 x [0] + 10 x [1] + x [2] manualmente não ganhará muito. Eu usei o que o @Daniel disse que era mais rápido, você mesmo pode verificar o código de referência.Eu resolveria o problema da seguinte maneira:
Aplicado à sua sequência de exemplo, isso gera:
Essa solução é executada em O (n) por n ser o comprimento da string fornecida e é, eu acho, o melhor que você pode obter.
fonte
Counter
. Você não precisa de umfinal_dict
e não precisa atualizá-lo a cada iteração.De acordo com o meu entendimento, você não pode ter a solução em um tempo constante. Será necessário pelo menos uma passagem sobre o número de um milhão de dígitos (supondo que seja uma string). Você pode ter uma iteração de rolagem de três dígitos sobre os dígitos do número de milhões de comprimentos e aumentar o valor da chave de hash em 1, se ele já existir, ou criar uma nova chave de hash (inicializada pelo valor 1), se ainda não existir. o dicionário.
O código será algo como isto:
Você pode filtrar até as chaves com valor de item maior que 1.
fonte
Como mencionado em outra resposta, você não pode executar esse algoritmo em tempo constante, porque deve procurar pelo menos n dígitos. O tempo linear é o mais rápido possível.
No entanto, o algoritmo pode ser feito em O (1) espaço . Você só precisa armazenar as contagens de cada número de 3 dígitos, portanto, precisa de uma matriz de 1000 entradas. Em seguida, você pode transmitir o número.
Meu palpite é que, ou o entrevistador falou errado quando lhe deram a solução, ou você ouviu "tempo constante" quando disse "espaço constante".
fonte
O(10**d)
espaço extra, onded
é o número de dígitos decimais que você está procurando.Aqui está a minha resposta:
O método de pesquisa de array é muito rápido (ainda mais rápido que o método numpy do @ paul-panzer!). Obviamente, ele trapaceia, pois não é tecnicamente terminado depois de concluído, porque está retornando um gerador. Ele também não precisa verificar todas as iterações se o valor já existir, o que provavelmente ajudará muito.
fonte
Counters
não são usados dessa maneira. Usados corretamente, eles se tornam a opção mais rápida com o seu exemplo. Se você usartimeit
com uma lista instalada de um gerador, seu método se tornará mais lento queCounter
oudict
. Veja aqui .f_array
pode ser mais rápido se primeiro converter todos os caracteres em int:ints = [int(c) for c in text]
e depois usari, j, k = ints[n:n+3]
.Imagem como resposta:
Parece uma janela deslizante.
fonte
Aqui está a minha solução:
Com um pouco de criatividade no loop for (e uma lista de pesquisa adicional com True / False / None, por exemplo), você poderá se livrar da última linha, pois só deseja criar chaves no dict que visitamos uma vez até aquele momento . Espero que ajude :)
fonte
-Dizer a partir da perspectiva de C. -Você pode obter resultados de uma matriz 3-d int [10] [10] [10]; -Vá do 0º local para o 4º local, onde n é o tamanho da matriz de cadeias. -Em cada local, verifique o atual, o próximo e o próximo é o próximo. -Incrementa o cntr como resutls [atual] [próximo] [próximo é próximo] ++; -Imprima os valores de
-É hora O (n), não há comparações envolvidas. -Você pode executar algumas coisas paralelas aqui particionando a matriz e calculando as correspondências em torno das partições.
fonte
fonte