J'ai eu un entretien avec une société de hedge funds à New York il y a quelques mois et malheureusement, je n'ai pas reçu l'offre de stage en tant qu'ingénieur data / logiciel. (Ils ont également demandé que la solution soit en Python.)
J'ai assez merdé sur le premier problème d'entretien ...
Question: Étant donné une chaîne d'un million de nombres (Pi par exemple), écrivez une fonction / programme qui renvoie tous les nombres répétitifs à 3 chiffres et le nombre de répétitions supérieur à 1
Par exemple: si la chaîne était: 123412345123456
alors la fonction / programme renverrait:
123 - 3 times
234 - 3 times
345 - 2 times
Ils ne m'ont pas donné la solution après avoir échoué à l'entretien, mais ils m'ont dit que la complexité temporelle de la solution était constante de 1000 puisque tous les résultats possibles sont compris entre:
000 -> 999
Maintenant que j'y pense, je ne pense pas qu'il soit possible de proposer un algorithme à temps constant. C'est ça?
la source
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
C'était probablement le test réel. Pour voir si vous pourriez leur prouver pourquoi cela n'est pas possible et leur montrer la complexité temporelle minimale correcte.Réponses:
Vous êtes descendu légèrement, vous ne voulez probablement pas travailler pour un hedge fund où les quants ne comprennent pas les algorithmes de base :-)
Il n'y a aucun moyen de traiter une structure de données de taille arbitraire
O(1)
si, comme dans ce cas, vous devez visiter chaque élément au moins une fois. Le mieux que vous puissiez espérer estO(n)
dans ce cas, oùn
est la longueur de la chaîne.Il me semble que vous auriez pu les impressionner de plusieurs façons.
D'abord, en les informant qu'il n'est pas possible de le faire
O(1)
, à moins que vous n'utilisiez le raisonnement «suspect» donné ci-dessus.Deuxièmement, en montrant vos compétences d'élite en fournissant un code pythonique tel que:
Cela produit:
bien que vous puissiez, bien sûr, modifier le format de sortie à tout ce que vous désirez.
Et, enfin, en leur disant qu'il n'y a presque certainement aucun problème avec une
O(n)
solution, puisque le code ci-dessus fournit des résultats pour une chaîne d'un million de chiffres en moins d'une demi-seconde. Il semble également être mis à l'échelle de manière assez linéaire, car une chaîne de 10 000 000 de caractères prend 3,5 secondes et une chaîne de 100 000 000 de caractères prend 36 secondes.Et, s'ils ont besoin de mieux que cela, il existe des moyens de paralléliser ce genre de choses qui peuvent considérablement l'accélérer.
Pas dans un seul interpréteur Python bien sûr, en raison du GIL, mais vous pouvez diviser la chaîne en quelque chose comme (le chevauchement indiqué par
vv
est nécessaire pour permettre un traitement approprié des zones de délimitation):Vous pouvez les regrouper pour séparer les travailleurs et combiner les résultats par la suite.
Le fractionnement de l'entrée et la combinaison de la sortie sont susceptibles de submerger toute économie avec de petites chaînes (et peut-être même des chaînes à millions de chiffres) mais, pour des ensembles de données beaucoup plus volumineux, cela peut bien faire une différence. Mon mantra habituel de «mesurer, ne pas deviner» s'applique ici, bien sûr.
Ce mantra s'applique également à d' autres possibilités, telles que le contournement de Python et l'utilisation d'un langage différent qui peut être plus rapide.
Par exemple, le code C suivant, exécuté sur le même matériel que le code Python précédent, gère cent millions de chiffres en 0,6 seconde, à peu près le même temps que le code Python en a traité un million. En d'autres termes, beaucoup plus rapide:
la source
O(1)
estn
fixe ou limitée.N
. Si vous le divisez en deux parties à la positionN/2
, vous devez toujours tenir compte du fait que vous pourriez manquer une correspondance valide à 3 chiffres à la «frontière», à la finstring1
et au début destring2
. Ainsi, vous devez vérifier les correspondances entrestring1[N/2-2]
etstring2[2]
(en utilisant un index de base zéro), etc. C'est l'idée.val -= 100 * (d[i]-'0');
pour supprimer le premier chiffre.val = 10*val + d[i+2]-'0'
pour accumuler un nouveau chiffre le moins significatif (analyse chaîne normale-> entier).val % 100
n'est peut-être pas horrible, mais seulement si100
est une constante de compilation, donc il n'utilise pas une vraie division HW.Le temps constant n'est pas possible. Tous les 1 million de chiffres doivent être examinés au moins une fois, c'est donc une complexité temporelle de O (n), où n = 1 million dans ce cas.
Pour une solution O (n) simple, créez un tableau de taille 1000 qui représente le nombre d'occurrences de chaque nombre à 3 chiffres possible. Avancez d'un chiffre à la fois, premier index == 0, dernier index == 999997 et incrémentez le tableau [numéro à 3 chiffres] pour créer un histogramme (nombre d'occurrences pour chaque numéro à 3 chiffres possible). Sortez ensuite le contenu du tableau avec des nombres> 1.
la source
x-'0'
modèle n'est pas valide en Python, c'est un C-ism (où les caractères sont des entiers).Un million est petit pour la réponse que je donne ci-dessous. En attendant seulement que vous deviez être en mesure d'exécuter la solution dans l'interview, sans pause, alors ce qui suit fonctionne en moins de deux secondes et donne le résultat souhaité:
Espérons que l'intervieweur recherchera l'utilisation des collections de bibliothèques standard.
Version d'exécution parallèle
J'ai écrit un article de blog à ce sujet avec plus d'explications.
la source
O(1)
.La solution simple O (n) serait de compter chaque nombre à 3 chiffres:
Cela permettrait de rechercher 1000 fois le million de chiffres.
Traverser les chiffres une seule fois:
Le chronométrage montre qu'itérer une seule fois sur l'index est deux fois plus rapide que d'utiliser
count
.la source
text.count()
?text.count
est fait dans un langage compilé à grande vitesse (par exemple C) par opposition à une boucle interprétée lente au niveau de python, oui il y a une remise.count
est incorrecte, car elle ne compte pas les motifs qui se chevauchent. Notez que'111'.count('11') == 1
lorsque nous nous attendons à ce qu'il le soit2
.O(n)
solution simple " est en faitO(10**d * n)
avecd
le nombre de chiffres recherchés etn
la longueur totale de la chaîne. Le second est leO(n)
temps et l'O(10**d + n)
espace.Voici une implémentation NumPy de l'algorithme «consensus» O (n): parcourez tous les triplets et bin au fur et à mesure. Le regroupement est effectué en rencontrant, disons "385", en ajoutant un au bin [3, 8, 5] qui est une opération O (1). Les bacs sont disposés dans un
10x10x10
cube. Comme le binning est entièrement vectorisé, il n'y a pas de boucle dans le code.Sans surprise, NumPy est un peu plus rapide que la solution pure Python de @ Daniel sur de grands ensembles de données. Exemple de sortie:
la source
ndarray
s, le type de base numpy, concerne le stockage, la manipulation et l'indexation efficaces de tableaux multidimensionnels de nombres. Parfois, vous pouvez raser quelques% en aplatissant, mais dans ce cas, faire 100 x [0] + 10 x [1] + x [2] à la main ne vous rapportera pas beaucoup. J'ai utilisé celui que @Daniel a dit était plus rapide, vous pouvez vérifier vous-même le code de référence.Je résoudrais le problème comme suit:
Appliqué à votre exemple de chaîne, cela donne:
Cette solution fonctionne dans O (n) pour n étant la longueur de la chaîne fournie, et est, je suppose, la meilleure que vous puissiez obtenir.
la source
Counter
. Vous n'avez pas besoin d'unfinal_dict
, et vous n'avez pas à le mettre à jour à chaque itération.Selon ma compréhension, vous ne pouvez pas avoir la solution dans un temps constant. Il faudra au moins un passage sur le nombre à millions de chiffres (en supposant qu'il s'agit d'une chaîne). Vous pouvez avoir une itération glissante à 3 chiffres sur les chiffres du nombre de millions de longueur et augmenter la valeur de la clé de hachage de 1 si elle existe déjà ou créer une nouvelle clé de hachage (initialisée par la valeur 1) si elle n'existe pas déjà dans le dictionnaire.
Le code ressemblera à ceci:
Vous pouvez filtrer jusqu'aux clés dont la valeur d'élément est supérieure à 1.
la source
Comme mentionné dans une autre réponse, vous ne pouvez pas faire cet algorithme en temps constant, car vous devez regarder au moins n chiffres. Le temps linéaire est le plus rapide que vous puissiez obtenir.
Cependant, l'algorithme peut être fait dans l' espace O (1) . Il vous suffit de stocker les nombres de chaque nombre à 3 chiffres, vous avez donc besoin d'un tableau de 1000 entrées. Vous pouvez ensuite diffuser le numéro au format.
Je suppose que soit l'intervieweur s'est mal exprimé lorsqu'il vous a donné la solution, soit vous avez mal entendu «temps constant» quand il a dit «espace constant».
la source
O(10**d)
un espace supplémentaire, oùd
est le nombre de chiffres décimaux que vous recherchez.Voici ma réponse:
La méthode de recherche de tableau est très rapide (encore plus rapide que la méthode numpy de @ paul-panzer!). Bien sûr, il triche car il n'est pas techniquement terminé après l'avoir terminé, car il renvoie un générateur. Il n'est pas non plus nécessaire de vérifier à chaque itération si la valeur existe déjà, ce qui est susceptible d'aider beaucoup.
la source
Counters
ne sont pas utilisés de cette façon. Utilisés correctement, ils deviennent l'option la plus rapide avec votre exemple. Si vous utiliseztimeit
avec une liste insted d'un générateur, votre méthode devient plus lente queCounter
oudict
. Regardez ici .f_array
pourriez être plus rapide si vous convertissez d'abord chaque caractère en un entier:ints = [int(c) for c in text]
puis utilisezi, j, k = ints[n:n+3]
.Image comme réponse:
On dirait une fenêtre coulissante.
la source
Voici ma solution:
Avec un peu de créativité dans la boucle for (et une liste de recherche supplémentaire avec True / False / None par exemple), vous devriez pouvoir vous débarrasser de la dernière ligne, car vous ne voulez créer que des clés dans dict que nous avons visitées une fois jusqu'à ce point . J'espère que ça aide :)
la source
-Dire du point de vue de C. -Vous pouvez avoir un tableau int 3-d résultats [10] [10] [10]; -Aller du 0ème emplacement au n-4ème emplacement, où n étant la taille du tableau de chaînes. -Sur chaque emplacement, vérifiez le courant, le suivant et le suivant. -Incrémenter le cntr comme resutls [courant] [suivant] [suivant suivant] ++; -Imprimer les valeurs de
-Il est temps O (n), il n'y a pas de comparaison impliquée. -Vous pouvez exécuter des trucs parallèles ici en partitionnant le tableau et en calculant les correspondances autour des partitions.
la source
la source