Frecuencia de palabras con ordenamiento en complejidad O (n)

11

Durante una entrevista para un puesto de desarrollador de Java, me preguntaron lo siguiente:

Escribe una función que tome dos parámetros:

  1. una cadena que representa un documento de texto y
  2. un número entero que proporciona la cantidad de artículos a devolver.

Implemente la función de modo que devuelva una lista de cadenas ordenadas por frecuencia de palabra, la palabra que aparece con más frecuencia primero. Su solución debe ejecutarse en tiempo donde n es el número de caracteres en el documento.O(n)n

Lo siguiente es lo que respondí (en pseudocódigo), no es sino tiempo O ( n log n ) debido al tipo. No puedo entender cómo hacerlo O ( n ) tiempo. O(n)O(nlogn)O(n)

wordFrequencyMap = new HashMap<String, Integer>();
words = inputString.split(' ');

for (String word : words) {
  count = wordFrequencyMap.get(word);
  count = (count == null) ? 1 : ++count;
  wordFrequencyMap.put(word, count);
}

return wordFrequencyMap.sortByValue.keys

¿Alguien sabe o alguien puede darme algunas pistas?

usuario2712937
fuente
1
Usa una tabla hash.
Yuval Filmus
Usar una tabla hash no resuelve el problema. Además, la tabla hash es Java heredado.
user2712937
Las tablas hash suelen ser el truco para reducir la complejidad de a O ( n ) . Incluso si son Java heredados, lo que sea que eso signifique. No he verificado este caso en particular, así que puede que tengas razón. O(nlogn)O(n)
Yuval Filmus
@YuvalFilmus. Gracias, pero la tabla hash es más o menos igual que el mapa hash, que ya estoy usando (la principal diferencia entre la estructura de 2 datos es la sincronización, que no se aplica aquí). El log (n) en el mío proviene de ordenar los valores en el mapa hash.
user2712937
3
Por cierto, este sitio se centra en conceptos y algoritmos, no en código. Por lo tanto, normalmente le pedimos que elimine el código Java y proporcione una descripción conceptual de su enfoque (posiblemente con un seudocódigo de alto nivel conciso si es necesario). Además, en este sitio, la pregunta relevante es qué estructuras de datos y algoritmos usar; la API Java específica está fuera de tema para este sitio (pero podría preguntar sobre ella en StackOverflow), y de manera similar, si Hashtablees Java heredado o no, es realmente irrelevante para los propósitos de este sitio.
DW

Respuestas:

10

Sugiero una variación del recuento de distribución:

  1. Lea el texto e inserte toda la palabra encontrada en un trie , manteniendo en cada nodo un recuento, con qué frecuencia se ha producido la palabra representada por este nodo. Además, realiza un seguimiento del mayor recuento de palabras, por ejemplo maxWordCound. - O(n)
  2. Inicializar una matriz de tamaño maxWordCount. El tipo de entrada son listas de cadenas. - , ya que el recuento no puede ser mayor.O(n)
  3. Atraviese el trie y para cada nodo agregue la cadena correspondiente a la entrada de matriz indicada por el recuento. - , ya que la longitud total de las cadenas está limitada por n .O(n)n
  4. Recorre la matriz en orden descendente y genera el número deseado de cadenas. - , ya que es un límite tanto en el tamaño como en la cantidad de datos en la matriz.O(n)

Probablemente pueda reemplazar el trie por otras estructuras de datos en la primera fase.

FrankW
fuente
+1, aunque no estoy seguro de esto. Es O (n) ya que el número de palabras para devolver está delimitado por n, el número de caracteres, pero ¿es esto lo que hace la pregunta? ¿O un resultado independiente del número de palabras devueltas?
Nikos M.
@NikosM. Que es ; es un límite superior general de mayúsculas y minúsculas en el número de palabras devueltas, no supuestos necesarios. n
Raphael
@Raphael, sí, correcto, estoy pensando en esto, ya que se le preguntó en una entrevista, posibles trucos en la pregunta ..
Nikos M.
Me pregunto si existe un algoritmo de tiempo lineal eficiente en el espacio.
saadtaame
3
O(nlgn)O(n)
3

La recopilación de recuentos de ocurrencias es O (n), por lo que el truco es realmente solo encontrar los mejores recuentos de ocurrencias k.

Un montón es una forma común de agregar los valores de k superiores, aunque se pueden usar otros métodos (ver https://en.wikipedia.org/wiki/Partial_sorting ).

Suponiendo que k es el segundo parámetro anterior, y que es una constante en la declaración del problema (parece ser):

  1. Cree un trie de palabras con recuentos de ocurrencias en cada nodo.
  2. Inicialice un montón de tamaño k.
  3. Atraviese el trie y la sonda mínima / inserte cada par (hoja, recuento de ocurrencia) en el montón superior-k.
  4. Genere las k hojas y recuentos superiores (esto es realmente un poco molesto porque necesita punteros principales para asignar cada hoja de nuevo a una palabra).

Como el tamaño de almacenamiento dinámico es constante, las operaciones de almacenamiento dinámico son O (1), por lo que el paso 3 es O (n).

El montón también podría mantenerse dinámicamente mientras se construye el trie.

KWillets
fuente
2

O(nlogn)Θ(n)Ω(n2)


Lo que sigue está mal ; Lo dejo aquí por el momento con fines ilustrativos.

O(n)Σn

  1. Construya un árbol de sufijos del texto, por ejemplo, con el algoritmo de Ukkonen .

    Si la construcción aún no hace esto, agregue el número de hojas alcanzables a cada nodo (interno).

  2. Atraviesa el árbol desde la raíz y corta todas las ramas en el primer espacio (blanco).

  3. Recorre el árbol y ordena la lista de hijos de cada nodo por sus recuentos de hojas.

  4. El rendimiento del árbol (hojas de izquierda a derecha) ahora es una lista de todas las palabras, ordenadas por frecuencia.

En cuanto al tiempo de ejecución:

  1. O(n)Θ costo del algoritmo.
  2. nn
  3. nO(|Σ|log|Σ|)=O(1)
  4. O(n)O(n)

Se pueden obtener límites más precisos parametrizando el tiempo de ejecución con el número de palabras diferentes; si hay pocos, el árbol es pequeño después de 2.

Rafael
fuente
El algoritmo es incorrecto (no se ordena). Ya no estoy seguro de que el tiempo lineal sea posible.
Raphael
1

HashMap1..nO(n)O(n)

O(n)O(n)O(n)

O(n)O(n)

DW
fuente
Θ(n)Ω(n2)
No puedo hablar por los entrevistadores, pero dudo en usar su descuido como excusa para más de lo mismo. Además, este sitio trata sobre la ciencia (como usted mismo comentó anteriormente), no sobre trucos de programación "cómo me pagarán antes".
Raphael
Mientras esta comprensión sea explícita, estoy de acuerdo con eso. He visto demasiadas preguntas aquí que se fundaron en la confusión porque alguna "comprensión" implícita promovió ideas erróneas.
Raphael
0

Solución basada en hash

Ω(n2)n

nΩ(n)

O(1)O(n)O(n2)n

La suposición es que el algoritmo de hash es lineal en el tiempo en relación con el número de caracteres.

Solución basada en clasificación Radix

O(kN)kNnkO(n)

2nnO(n)

Las primeras palabras más largas en inglés son ridículamente largas , pero luego se puede limitar la longitud de la palabra en un número razonable (como 30 o menos) y truncar las palabras aceptando el margen de error que podría venir con ella.

Omer Iqbal
fuente
Θ(n)Θ(n)
O(n+n)O(n2)
(3) Cualquiera que sea la función hash que elija, se me ocurre una entrada donde esa función específica se degrada. Y elegir la función hash después de conocer la entrada generalmente no es una opción. (Y recuerde que el comentario que presumiblemente estaba abordando era sobre el peor de los casos, no el caso típico)
FrankW
O(n2)
O(n2)O(1)Ω(1)O(1)O(1)