Fréquence des mots avec ordre dans la complexité O (n)


11

Lors d'une interview pour un poste de développeur Java, on m'a demandé ce qui suit:

Écrivez une fonction qui prend deux paramètres:

  1. une chaîne représentant un document texte et
  2. un entier indiquant le nombre d'articles à retourner.

Implémentez la fonction de telle sorte qu'elle retourne une liste de chaînes ordonnées par fréquence de mot, le mot le plus fréquent en premier. Votre solution doit s'exécuter en temps où est le nombre de caractères dans le document.O(n)n

Ce qui suit est ce que j'ai répondu (en pseudocode), ce n'est pas mais plutôt raison du tri. Je ne peux pas comprendre comment le faire temps. O(n)O(nJournaln)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

Quelqu'un sait-il ou quelqu'un peut-il me donner des indices?


1
Utilisez une table de hachage.
Yuval Filmus

L'utilisation d'une table de hachage ne résout pas le problème. De plus, la table de hachage est Java héritée.
user2712937

Les tables de hachage sont généralement l'astuce pour faire descendre la complexité de à O ( n ) . Même s'il s'agit de Java hérité, quoi que cela signifie. Je n'ai pas vérifié ce cas particulier, vous avez donc peut-être raison. O(nlogn)O(n)
Yuval Filmus

@YuvalFilmus. Merci mais la table de hachage est à peu près la même que la carte de hachage, que j'utilise déjà (la différence majeure entre la structure de 2 données est la synchronisation, qui ne s'applique pas ici). Le journal (n) dans le mien provient du tri des valeurs dans la carte de hachage.
user2712937

3
Soit dit en passant, ce site se concentre sur les concepts et les algorithmes, pas sur le code. Par conséquent, nous vous demandons normalement de supprimer le code Java et de donner une description conceptuelle de votre approche (éventuellement avec un pseudocode de haut niveau concis si nécessaire). De plus, sur ce site, la question pertinente est de savoir quelles structures de données et quels algorithmes utiliser; l'API Java spécifique est hors sujet pour ce site (mais vous pouvez vous renseigner à ce sujet sur StackOverflow), et de même, le fait que HashtableJava hérité ou non soit vraiment hors de propos pour les besoins de ce site.
DW

Réponses:


10

Je suggère une variation du comptage de distribution:

  1. Lisez le texte et insérez tous les mots rencontrés dans un trie , en conservant dans chaque nœud un décompte, à quelle fréquence le mot représenté par ce nœud s'est produit. De plus, gardez une trace du nombre de mots le plus élevé maxWordCound. - O(n)
  2. Initialisez un tableau de taille maxWordCount. Le type d'entrée est une liste de chaînes. - , car le nombre ne peut pas être supérieur.O(n)
  3. Parcourez le trie et pour chaque nœud, ajoutez la chaîne correspondante à l'entrée du tableau indiquée par le nombre. - , car la longueur totale des chaînes est limitée par n .O(n)n
  4. Parcourez le tableau dans l'ordre décroissant et affichez le nombre de chaînes souhaité. - , car il s'agit d'une limite à la fois de la taille et de la quantité de données dans le tableau.O(n)

Vous pouvez probablement remplacer le trie par d'autres structures de données dans la première phase.


+1, bien que je n'en sois pas sûr. C'est O (n) puisque le nombre de mots à renvoyer est borné par n, le nombre de caractères, mais est-ce ce que la question pose? Ou un résultat indépendant du nombre de mots retournés?
Nikos M.

@NikosM. Il est ; est une limite supérieure générale du pire des cas sur le nombre de mots renvoyés, et non des hypothèses nécessaires. n
Raphael

@Raphael, yeap correct je pense à cela car il a été demandé dans une interview, des astuces possibles dans la question ..
Nikos M.

Je me demande s'il existe un algorithme de temps linéaire économe en espace.
saadtaame

3
@saadtaame, yup, c'est une question intéressante. Cela pourrait valoir la peine d'être publié séparément en tant que question distincte. Ce n'est pas seulement l'efficacité de l'espace; la solution trie est également gourmande en pointeurs, ce qui pourrait la ralentir dans la pratique (étant donné le fonctionnement de la hiérarchie de la mémoire dans les machines réelles). L '"efficacité" est différente de la durée d'exécution la plus défavorable. Il n'est pas inhabituel qu'un algorithme de temps propre batte un algorithme de temps O ( n ) intensif en pointeurs , donc cette question semble déjà exclure certains algorithmes potentiels qui pourraient être un meilleur choix dans la pratique. O(nlgn)O(n)
DW

3

La collecte des nombres d'occurrences est O (n), donc l'astuce consiste vraiment à trouver uniquement les k premiers nombres d'occurrences.

Un tas est un moyen courant d'agréger les k premières valeurs, bien que d'autres méthodes puissent être utilisées (voir https://en.wikipedia.org/wiki/Partial_sorting ).

En supposant que k est le deuxième paramètre ci-dessus, et que c'est une constante dans l'énoncé du problème (il semble que ce soit):

  1. Créez un trie de mots avec un nombre d'occurrences sur chaque nœud.
  2. Initialisez un tas de taille k.
  3. Parcourez le trie et le min-sonde / insérez chaque paire (feuille, nombre d'occurrences) dans le tas du haut k.
  4. Sortez les k premières feuilles et compte (c'est en fait une sorte de douleur car vous avez besoin de pointeurs parents pour mapper chaque feuille en un mot).

Étant donné que la taille du tas est une constante, les opérations du tas sont O (1), donc l'étape 3 est O (n).

Le tas pourrait également être maintenu dynamiquement lors de la construction du trie.


2

Votre algorithme ne s'exécute même pas dans le temps ; insertion thetav ( n ) les choses dans un temps frais Hashtable Ω ( n 2 ) déjà (pire cas).O(nlogn)Θ(n)Ω(n2)


Ce qui suit est faux ; Je le laisse ici pour le moment à des fins d'illustration.

L'algorithme suivant s'exécute dans le pire des cas (en supposant un alphabet Σ de taille constante), n le nombre de caractères dans le texte.O(n)Σn

  1. Construisez un arbre de suffixes du texte, par exemple avec l'algorithme d' Ukkonen .

    Si la construction ne le fait pas déjà, ajoutez le nombre de feuilles accessibles à chaque nœud (interne).

  2. Traversez l'arbre de la racine et coupez toutes les branches au premier espace (blanc).

  3. Parcourez l'arbre et triez la liste des enfants de chaque nœud en fonction de leur nombre de feuilles.

  4. Le rendement de l'arbre (feuilles de gauche à droite) est maintenant une liste de tous les mots, triés par fréquence.

Concernant l'exécution:

  1. L'algorithme d'Ukkonen (dans sa forme améliorée) s'exécute dans le temps ; le maintien du nombre de feuilles n'augmente pas le coût Θ de l'algorithme.O(n)Θ
  2. Nous devons traverser un nœud par caractère de chaque mot qui apparaît dans le texte. Puisqu'il y a au plus paires de mots-caractères différentes, nous visitons au plus n nœuds.nn
  3. Nous visitons au plus nœuds (cf. 2.) et passons du temps O ( | Σ |log | Σ | ) = O ( 1 ) par nœud.nO(|Σ|log|Σ|)=O(1)
  4. On peut obtenir le rendement (qui a bien sûr la taille ) par une simple traversée dans le temps O ( n ) (cf. 2.).O(n)O(n)

Des limites plus précises peuvent être obtenues en paramétrant l'exécution avec le nombre de mots différents; s'il y en a peu, l'arbre est petit après 2.


L'algorithme est incorrect (il ne trie pas). Je ne suis plus sûr que le temps linéaire soit même possible.
Raphael

1

Utilisez une table de hachage (par exemple, HashMap) pour collecter tous les mots et leurs fréquences. Utilisez ensuite le tri par comptage pour trier les mots par ordre décroissant de fréquence. Comme toutes les fréquences sont des entiers compris entre , le tri du comptage prend O ( n ) . Le temps de fonctionnement total prévu est O ( n ) , ce qui est probablement plus que suffisant pour toutes les fins pratiques (à moins que l'enquêteur n'ait mentionné quelque chose qui a été omis de votre question). Assurez-vous de mentionner qu'il s'agit du temps d' exécution prévu plutôt que du pire des cas .1..nO(n)O(n)

Ce n'est peut-être pas la réponse qu'un enseignant chercherait dans une classe d'algorithmes, car il s'agit du temps d'exécution plutôt que du temps d'exécution O ( n ) dans le pire des cas. Si vous souhaitez marquer des points supplémentaires à la question d'entrevue, vous pouvez mentionner de manière désinvolte de manière désinvolte que cela est bien sûr le temps de fonctionnement prévu, mais cela peut également être fait dans le temps de fonctionnement le plus défavorable O ( n ) en remplaçant le table de hachage avec une structure de données plus sophistiquée - et vous seriez heureux d'expliquer comment vous choisiriez entre les algorithmes dans une situation comme celle-ci.O(n)O(n)O(n)

O(n)O(n)


Θ(n)Ω(n2)

Je ne peux pas parler au nom des enquêteurs, mais j'hésite à utiliser leur négligence comme excuse pour la même chose. De plus, ce site concerne la science (comme vous l'avez vous-même commenté ci-dessus), pas les astuces de programmation "comment vais-je être payé plus tôt".
Raphael

Tant que cette compréhension est explicite, je suis d'accord avec cela. J'ai vu ici trop de questions qui étaient fondées dans la confusion parce qu'une certaine «compréhension» implicite a favorisé des idées fausses.
Raphael

0

Solution basée sur Hashtable

Ω(n2)n

nΩ(n)

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

L'hypothèse est que l'algorithme de hachage est linéaire dans le temps par rapport au nombre de caractères.

Solution basée sur le tri Radix

O(kN)kNnkO(n)

2nnO(n)

Les quelques mots les plus longs en anglais sont ridiculement longs , mais alors on pourrait limiter la longueur du mot à un nombre raisonnable (tel que 30 ou plus petit) et tronquer les mots en acceptant la marge d'erreur qui pourrait l'accompagner.


Θ(n)Θ(n)

O(n+n)O(n2)

(3) Quelle que soit la fonction de hachage que vous choisissez, je peux proposer une entrée où cette fonction spécifique se dégrade. Et choisir la fonction de hachage après avoir connu l'entrée n'est généralement pas une option. (Et rappelez-vous que le commentaire que vous
adressiez

O(n2)

O(n2)O(1)Ω(1)O(1)O(1)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.