J'ai eu une interview avec une société de fonds spéculatifs à New York il y a quelques mois et, malheureusement, je n'ai pas reçu l'offre de stage en tant qu'ingénieur en informatique/logiciel. (Ils ont également demandé à la solution d'être en Python.)
J'ai pas mal foiré le premier problème d'interview ...
Question: Soit une chaîne d'un million de nombres (Pi par exemple), écrivez une fonction/programme qui renvoie tous les nombres à 3 chiffres répétés et le nombre de répétition supérieure à 1
Par exemple: si la chaîne était: 123412345123456
, la fonction/programme renverrait:
123 - 3 times
234 - 3 times
345 - 2 times
Ils ne m'ont pas donné la solution après l'échec de l'entretien, mais ils m'ont dit que la complexité temporelle de la solution était constante, car tous les résultats possibles se situent entre:
000 -> 999
Maintenant que j'y réfléchis, je ne pense pas qu'il soit possible de concevoir un algorithme à temps constant. Est ce
Le temps constant n'est pas possible. Tous les 1 million de chiffres doivent être examinés au moins une fois, il s'agit donc d'une complexité temporelle de O (n), où n = 1 million dans ce cas.
Pour une solution simple O(n), créez un tableau de taille 1000 représentant le nombre d'occurrences de chaque nombre possible de 3 chiffres. Faites avancer les chiffres d’un chiffre à l’autre, le premier index == 0, le dernier index == 999997 et incrémentez le tableau [nombre à 3 chiffres] pour créer un histogramme (nombre d’occurrences pour chaque nombre possible de 3 chiffres). Puis, affichez le contenu du tableau avec un nombre> 1.
La solution simple O(n) consisterait à compter chaque nombre à 3 chiffres:
for nr in range(1000):
cnt = text.count('%03d' % nr)
if cnt > 1:
print '%03d is found %d times' % (nr, cnt)
Cela rechercherait à travers 1 million de chiffres 1000 fois.
En parcourant les chiffres une seule fois:
counts = [0] * 1000
for idx in range(len(text)-2):
counts[int(text[idx:idx+3])] += 1
for nr, cnt in enumerate(counts):
if cnt > 1:
print '%03d is found %d times' % (nr, cnt)
Le minutage montre qu'itérer une seule fois sur l'index est deux fois plus rapide que d'utiliser count
.
Un million est petit pour la réponse que je donne ci-dessous. Attendez-vous à ce que vous deviez pouvoir exécuter la solution dans l'interview, sans pause, puis procédez comme suit:
from collections import Counter
def triple_counter(s):
c = Counter(s[n-3: n] for n in range(3, len(s)))
for tri, n in c.most_common():
if n > 1:
print('%s - %i times.' % (tri, n))
else:
break
if __== '__main__':
import random
s = ''.join(random.choice('0123456789') for _ in range(1_000_000))
triple_counter(s)
J'espère que l'intervieweur cherchera à utiliser les collections standard.Counter de la bibliothèque standard.
J'ai écrit un blog post à ce sujet avec plus d'explications.
Voici une implémentation NumPy de l'algorithme "consensus" O(n): parcourez tous les triplets et la corbeille au fur et à mesure. Le binning est fait en rencontrant le mot "385", en ajoutant un à bin [3, 8, 5] qui est une opération O(1). Les bacs sont disposés dans un cube 10x10x10
. Comme le binning est entièrement vectorisé, il n'y a pas de boucle dans le code.
def setup_data(n):
import random
digits = "0123456789"
return dict(text = ''.join(random.choice(digits) for i in range(n)))
def f_np(text):
# Get the data into NumPy
import numpy as np
a = np.frombuffer(bytes(text, 'utf8'), dtype=np.uint8) - ord('0')
# Rolling triplets
a3 = np.lib.stride_tricks.as_strided(a, (3, a.size-2), 2*a.strides)
bins = np.zeros((10, 10, 10), dtype=int)
# Next line performs O(n) binning
np.add.at(bins, Tuple(a3), 1)
# Filtering is left as an exercise
return bins.ravel()
def f_py(text):
counts = [0] * 1000
for idx in range(len(text)-2):
counts[int(text[idx:idx+3])] += 1
return counts
import numpy as np
import types
from timeit import timeit
for n in (10, 1000, 1000000):
data = setup_data(n)
ref = f_np(**data)
print(f'n = {n}')
for name, func in list(globals().items()):
if not name.startswith('f_') or not isinstance(func, types.FunctionType):
continue
try:
assert np.all(ref == func(**data))
print("{:16s}{:16.8f} ms".format(name[2:], timeit(
'f(**data)', globals={'f':func, 'data':data}, number=10)*100))
except:
print("{:16s} apparently crashed".format(name[2:]))
Sans surprise, NumPy est un peu plus rapide que la solution pure Python de @ Daniel sur les grands ensembles de données. Exemple de sortie:
# n = 10
# np 0.03481400 ms
# py 0.00669330 ms
# n = 1000
# np 0.11215360 ms
# py 0.34836530 ms
# n = 1000000
# np 82.46765980 ms
# py 360.51235450 ms
Je voudrais résoudre le problème comme suit:
def find_numbers(str_num):
final_dict = {}
buffer = {}
for idx in range(len(str_num) - 3):
num = int(str_num[idx:idx + 3])
if num not in buffer:
buffer[num] = 0
buffer[num] += 1
if buffer[num] > 1:
final_dict[num] = buffer[num]
return final_dict
Appliqué à votre exemple de chaîne, cela donne:
>>> find_numbers("123412345123456")
{345: 2, 234: 3, 123: 3}
Cette solution est exécutée dans O(n), n étant la longueur de la chaîne fournie et, je suppose, la meilleure solution.
Selon ma compréhension, vous ne pouvez pas avoir la solution dans un temps constant. Il faudra au moins un passage sur le nombre de millions de chiffres (en supposant que ce soit une chaîne). Vous pouvez avoir une itération mobile de 3 chiffres sur les chiffres du nombre de longueurs en millions 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:
def calc_repeating_digits(number):
hash = {}
for i in range(len(str(number))-2):
current_three_digits = number[i:i+3]
if current_three_digits in hash.keys():
hash[current_three_digits] += 1
else:
hash[current_three_digits] = 1
return hash
Vous pouvez filtrer jusqu'aux clés dont la valeur d’élément est supérieure à 1.
Comme mentionné dans une autre réponse, vous ne pouvez pas utiliser cet algorithme en temps constant, car vous devez examiner au moins n chiffres. Le temps linéaire est le plus rapide que vous puissiez obtenir.
Cependant, l'algorithme peut être réalisé dans O(1) space . Il vous suffit de stocker les nombres de chaque numéro à 3 chiffres, vous avez donc besoin d'un tableau de 1 000 entrées. Vous pouvez ensuite diffuser le numéro dans.
Je suppose que l'intervieweur s'est mal exprimé lorsqu'il vous a donné la solution ou que vous avez mal compris le "temps constant" lorsqu'il a dit "espace constant"
Voici ma réponse:
from timeit import timeit
from collections import Counter
import types
import random
def setup_data(n):
digits = "0123456789"
return dict(text = ''.join(random.choice(digits) for i in range(n)))
def f_counter(text):
c = Counter()
for i in range(len(text)-2):
ss = text[i:i+3]
c.update([ss])
return (i for i in c.items() if i[1] > 1)
def f_dict(text):
d = {}
for i in range(len(text)-2):
ss = text[i:i+3]
if ss not in d:
d[ss] = 0
d[ss] += 1
return ((i, d[i]) for i in d if d[i] > 1)
def f_array(text):
a = [[[0 for _ in range(10)] for _ in range(10)] for _ in range(10)]
for n in range(len(text)-2):
i, j, k = (int(ss) for ss in text[n:n+3])
a[i][j][k] += 1
for i, b in enumerate(a):
for j, c in enumerate(b):
for k, d in enumerate(c):
if d > 1: yield (f'{i}{j}{k}', d)
for n in (1E1, 1E3, 1E6):
n = int(n)
data = setup_data(n)
print(f'n = {n}')
results = {}
for name, func in list(globals().items()):
if not name.startswith('f_') or not isinstance(func, types.FunctionType):
continue
print("{:16s}{:16.8f} ms".format(name[2:], timeit(
'results[name] = f(**data)', globals={'f':func, 'data':data, 'results':results, 'name':name}, number=10)*100))
for r in results:
print('{:10}: {}'.format(r, sorted(list(results[r]))[:5]))
La méthode de recherche de tableau est très rapide (même plus rapide que la méthode numpy de @ paul-panzer!). Bien sûr, il triche car il n’est pas terminé techniquement après l’achèvement, car il renvoie un générateur. De plus, il n'est pas nécessaire de vérifier chaque itération si la valeur existe déjà, ce qui aidera probablement beaucoup.
n = 10
counter 0.10595780 ms
dict 0.01070654 ms
array 0.00135370 ms
f_counter : []
f_dict : []
f_array : []
n = 1000
counter 2.89462101 ms
dict 0.40434612 ms
array 0.00073838 ms
f_counter : [('008', 2), ('009', 3), ('010', 2), ('016', 2), ('017', 2)]
f_dict : [('008', 2), ('009', 3), ('010', 2), ('016', 2), ('017', 2)]
f_array : [('008', 2), ('009', 3), ('010', 2), ('016', 2), ('017', 2)]
n = 1000000
counter 2849.00500992 ms
dict 438.44007806 ms
array 0.00135370 ms
f_counter : [('000', 1058), ('001', 943), ('002', 1030), ('003', 982), ('004', 1042)]
f_dict : [('000', 1058), ('001', 943), ('002', 1030), ('003', 982), ('004', 1042)]
f_array : [('000', 1058), ('001', 943), ('002', 1030), ('003', 982), ('004', 1042)]
Image comme réponse:
On dirait une fenêtre coulissante.
Voici ma solution:
from collections import defaultdict
string = "103264685134845354863"
d = defaultdict(int)
for elt in range(len(string)-2):
d[string[elt:elt+3]] += 1
d = {key: d[key] for key in d.keys() if d[key] > 1}
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 être en mesure de vous débarrasser de la dernière ligne, car vous voulez uniquement créer des clés dans le dict que nous avons visité une fois jusqu'à ce point. .J'espère que ça aide :)
-Dire du point de vue de C .- Vous pouvez avoir un résultat de tableau entier sur 3-d [10] [10] [10]; - Aller du 0ème au n-4ème emplacement, où n étant le taille du tableau de chaînes .- Sur chaque emplacement, vérifiez les valeurs actuelle, suivante et suivante .- Incrémentez le cntr en tant que résultats [actuel] [suivant] [suivant] [suivant] ++; - Imprimer les valeurs de
results[1][2][3]
results[2][3][4]
results[3][4][5]
results[4][5][6]
results[5][6][7]
results[6][7][8]
results[7][8][9]
-Il est temps O(n), il n'y a pas de comparaisons impliquées .- Vous pouvez exécuter des trucs parallèles ici en partitionnant le tableau et en calculant les correspondances autour des partitions.