Hace unos meses tuve una entrevista con una compañía de fondos de cobertura en Nueva York y desafortunadamente no recibí la oferta de pasantía como ingeniero de datos / software. (También pidieron que la solución estuviera en Python).
Me equivoqué bastante con el primer problema de la entrevista ...
Pregunta: Dada una cadena de un millón de números (Pi, por ejemplo), escriba una función / programa que devuelva todos los números repetidos de 3 dígitos y el número de repetición mayor que 1
Por ejemplo: si la cadena era: 123412345123456
entonces la función / programa devolvería:
123 - 3 times
234 - 3 times
345 - 2 times
No me dieron la solución después de que fallé la entrevista, pero sí me dijeron que la complejidad del tiempo para la solución era constante de 1000 ya que todos los resultados posibles están entre:
000 -> 999
Ahora que lo estoy pensando, no creo que sea posible crear un algoritmo de tiempo constante. ¿Lo es?
fuente
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
Esta fue probablemente la prueba real. Para ver si puede demostrarles por qué esto no es posible y mostrarles la complejidad de tiempo mínima correcta.Respuestas:
Saliste a la ligera, probablemente no quieras estar trabajando para un fondo de cobertura donde los quants no entienden los algoritmos básicos :-)
No hay forma de procesar una estructura de datos de tamaño arbitrario
O(1)
si, como en este caso, necesita visitar cada elemento al menos una vez. Lo mejor que puede esperar esO(n)
en este caso, donden
está la longitud de la cadena.Me parece que podría haberlos impresionado de varias maneras.
En primer lugar, informándoles que es no es posible hacerlo en
O(1)
, a menos que utilice el "sospechoso" razonamiento expuesto anteriormente.En segundo lugar, al mostrar sus habilidades de élite al proporcionar un código Pythonic como:
Esto produce:
aunque podría, por supuesto, modificar el formato de salida a lo que desee.
Y, finalmente, al decirles que casi con seguridad no hay problema con una
O(n)
solución, ya que el código anterior ofrece resultados para una cadena de un millón de dígitos en menos de medio segundo. Parece escalar también de manera bastante lineal, ya que una cadena de 10,000,000 caracteres toma 3.5 segundos y una de 100,000,000 caracteres toma 36 segundos.Y, si necesitan algo mejor que eso, hay formas de paralelizar este tipo de cosas que pueden acelerarlo enormemente.
No dentro de un solo intérprete de Python, por supuesto, debido a la GIL, pero podría dividir la cadena en algo como (
vv
se requiere una superposición indicada para permitir el procesamiento adecuado de las áreas límite):Puede cultivarlos para separar a los trabajadores y luego combinar los resultados.
Es probable que la división de la entrada y la combinación de la salida empañen cualquier ahorro con cadenas pequeñas (y posiblemente cadenas de incluso millones de dígitos) pero, para conjuntos de datos mucho más grandes, bien puede hacer la diferencia. Mi mantra habitual de "medir, no adivinar" se aplica aquí, por supuesto.
Este mantra también se aplica a otras posibilidades, como evitar Python por completo y usar un lenguaje diferente que puede ser más rápido.
Por ejemplo, el siguiente código C, que se ejecuta en el mismo hardware que el código Python anterior, maneja cien millones de dígitos en 0.6 segundos, aproximadamente la misma cantidad de tiempo que el código Python procesó un millón. En otras palabras, mucho más rápido:
fuente
O(1)
sen
está fija o limitada.N
. Si lo divide en dos partes en la posiciónN/2
, aún debe tener en cuenta el hecho de que podría perderse una coincidencia válida de 3 dígitos en el "borde", al finalstring1
y al principio destring2
. Por lo tanto, debe verificar las coincidencias entrestring1[N/2-2]
ystring2[2]
(usando un índice basado en cero), etc. Esa es la idea.val -= 100 * (d[i]-'0');
para soltar el dígito inicial.val = 10*val + d[i+2]-'0'
para acumular un nuevo dígito menos significativo (cadena normal-> análisis de enteros).val % 100
posiblemente no sea horrible, pero solo si100
es una constante de tiempo de compilación para que no use una división HW real.El tiempo constante no es posible. Todos los 1 millón de dígitos deben observarse al menos una vez, por lo que es una complejidad temporal de O (n), donde n = 1 millón en este caso.
Para una solución O (n) simple, cree una matriz de tamaño 1000 que represente el número de ocurrencias de cada posible número de 3 dígitos. Avance 1 dígito a la vez, primer índice == 0, último índice == 999997 e incremente la matriz [número de 3 dígitos] para crear un histograma (recuento de ocurrencias para cada posible número de 3 dígitos). Luego, muestre el contenido de la matriz con conteos> 1.
fuente
x-'0'
patrón no es válido en Python, es un C-ismo (donde los caracteres son enteros).Un millón es pequeño para la respuesta que doy a continuación. Esperando solo que debe poder ejecutar la solución en la entrevista, sin pausa, entonces lo siguiente funciona en menos de dos segundos y da el resultado requerido:
Esperemos que el entrevistador esté buscando el uso de las colecciones de bibliotecas estándar. Clase de contadores.
Versión de ejecución paralela
Escribí una publicación de blog sobre esto con más explicaciones.
fuente
O(1)
.La solución simple de O (n) sería contar cada número de 3 dígitos:
Esto buscaría todos los 1 millón de dígitos 1000 veces.
Recorriendo los dígitos solo una vez:
El tiempo muestra que iterar solo una vez sobre el índice es dos veces más rápido que usarlo
count
.fuente
text.count()
?text.count
se realiza en un lenguaje compilado de alta velocidad (por ejemplo, C) en lugar de un bucle interpretado lento a nivel de python, sí, hay un descuento.count
es incorrecta, ya que no contará con patrones superpuestos. Tenga en cuenta que'111'.count('11') == 1
cuando esperaríamos que sea2
.O(n)
solución simple " es en realidadO(10**d * n)
cond
el número de dígitos buscados yn
la longitud total de la cadena. El segundo es elO(n)
tiempo y elO(10**d + n)
espacio.Aquí hay una implementación NumPy del algoritmo de "consenso" O (n): recorra todos los tripletes y bin a medida que avanza. El binning se realiza al encontrar, digamos "385", agregando uno al bin [3, 8, 5] que es una operación O (1). Los contenedores están dispuestos en un
10x10x10
cubo. Como el binning está completamente vectorizado, no hay bucle en el código.Como era de esperar, NumPy es un poco más rápido que la solución Python pura de @Daniel en grandes conjuntos de datos. Salida de muestra:
fuente
ndarray
s, el tipo de núcleo numpy, se trata de almacenamiento eficiente, manipulación e indexación de matrices multidimensionales de números. A veces puedes reducir un poco el% aplanando, pero en este caso hacer 100 x [0] + 10 x [1] + x [2] a mano no te dará mucho. Usé el que @Daniel dijo que era más rápido, puedes verificar el código de referencia tú mismo.Resolvería el problema de la siguiente manera:
Aplicado a su cadena de ejemplo, esto produce:
Esta solución se ejecuta en O (n) porque n es la longitud de la cadena proporcionada y es, supongo, lo mejor que puede obtener.
fuente
Counter
. No necesita unfinal_dict
, y no tiene que actualizarlo en cada iteración.Según tengo entendido, no puede tener la solución en un tiempo constante. Tomará al menos una pasada sobre el número de un millón de dígitos (suponiendo que sea una cadena). Puede tener una iteración continua de 3 dígitos sobre los dígitos del número de millones de longitud y aumentar el valor de la clave hash en 1 si ya existe o crear una nueva clave hash (inicializada por el valor 1) si aún no existe en el diccionario.
El código se verá así:
Puede filtrar a las teclas que tienen un valor de elemento mayor que 1.
fuente
Como se mencionó en otra respuesta, no puede hacer este algoritmo en tiempo constante, porque debe mirar al menos n dígitos. El tiempo lineal es lo más rápido que puede obtener.
Sin embargo, el algoritmo se puede hacer en el espacio O (1) . Solo necesita almacenar los recuentos de cada número de 3 dígitos, por lo que necesita una matriz de 1000 entradas. Luego puede transmitir el número.
Supongo que o bien el entrevistador habló mal cuando le dieron la solución, o usted escuchó mal el "tiempo constante" cuando dijo "espacio constante".
fuente
O(10**d)
espacio adicional, donded
está el número de dígitos decimales que está buscando.Aquí está mi respuesta:
El método de búsqueda de matriz es muy rápido (¡incluso más rápido que el método numpy de @paul-panzer!). Por supuesto, hace trampa ya que no está técnicamente terminado después de que se completa, porque está devolviendo un generador. Tampoco tiene que verificar cada iteración si el valor ya existe, lo que probablemente ayudará mucho.
fuente
Counters
no se usan de esa manera. Usados correctamente, se convierten en la opción más rápida con su ejemplo. Si usatimeit
una lista en lugar de un generador, su método se vuelve más lento queCounter
odict
. Ver aquí .f_array
podría ser más rápido si primero convierte cada carácter en un int:ints = [int(c) for c in text]
y luego lo usai, j, k = ints[n:n+3]
.Imagen como respuesta:
Parece una ventana deslizante.
fuente
Aquí está mi solución:
Con un poco de creatividad en el bucle for (y una lista de búsqueda adicional con True / False / None, por ejemplo), debería poder deshacerse de la última línea, ya que solo desea crear claves en dict que visitamos una vez hasta ese momento . Espero eso ayude :)
fuente
-Dirigiendo desde la perspectiva de C. -Puede tener una matriz int 3-d resultados [10] [10] [10]; -Vaya de la ubicación 0 a la ubicación n-4, donde n es el tamaño de la matriz de cadenas. -En cada ubicación, verifique la siguiente, la siguiente y la siguiente. -Incremente el cntr como resultados [actual] [siguiente] [siguiente es el siguiente] ++; -Imprimir los valores de
-Es hora (n), no hay comparaciones involucradas. -Puede ejecutar algunas cosas paralelas aquí al particionar la matriz y calcular las coincidencias alrededor de las particiones.
fuente
fuente