On m'a posé cette question lors d'une projection téléphonique technique récemment et je n'ai pas bien fait. La question est incluse mot pour mot ci-dessous.
Générez une
{2^i * 5^j | i,j >= 0}
collection triée. Imprimez en continu la plus petite valeur suivante.Exemple:
{ 1, 2, 4, 5, 8, 10...}
«Le plus petit suivant» me fait penser qu'il y a un tas de min, mais je ne savais pas vraiment où aller à partir de là et aucune aide n'a été fournie par l'intervieweur.
Quelqu'un a-t-il des conseils sur la façon de résoudre un tel problème?
algorithms
Justin Skiles
la source
la source
Réponses:
Reformulons le problème: sortie de chaque nombre de 1 à l'infini de telle sorte que le nombre n'a aucun facteur sauf 2 et 5.
Ci-dessous, un simple extrait C #:
L' approche de Kilian / QuestionC est beaucoup plus performante. Extrait C # avec cette approche:
SortedSet
empêche les insertions en double.Fondamentalement, cela fonctionne en s'assurant que le numéro suivant de la séquence est bien
itms
.Preuve de la validité de cette approche:
l'algorithme décrit garantit qu'après n'importe quel nombre sorti dans le formulaire
2^i*5^j
, l'ensemble contient maintenant2^(i+1)*5^j
et2^i*5^(j+1)
. Supposons que le nombre suivant de la séquence soit2^p*5^q
. Il doit exister un numéro de sortie précédemment de la forme2^(p-1)*5^(q)
ou2^p*5^(q-1)
(ou les deux, si ni p ni q ne sont égaux à 0). Sinon, ce2^p*5^q
n'est pas le numéro suivant, car2^(p-1)*5^(q)
et2^p*5^(q-1)
sont tous deux plus petits.Le deuxième extrait utilise de la
O(n)
mémoire (où n est le nombre de nombres qui ont été sortis), carO(i+j) = O(n)
(car i et j sont tous deux inférieurs à n), et trouvera n nombres dans leO(n log n)
temps. Le premier extrait trouve des nombres dans un temps exponentiel.la source
1 = 2^0*5^0, 2 = 2^1*5^0, 4 = 2^2*5^0, 5 = 2^0*5^1, 8 = 2^3*5^0, 10 = 2^1*5^1
..Remove()
et.Add()
va déclencher un mauvais comportement de la part du garbage collector, ou est-ce que ça va comprendre les choses?Il s'agit d'une question d'entrevue suffisamment courante pour qu'il soit utile de connaître la réponse. Voici l'entrée pertinente dans ma feuille de lit personnel:
En d'autres termes, vous avez besoin d'une approche en deux étapes avec un tampon trié supplémentaire pour résoudre ce problème efficacement. (Une bonne description plus longue est dans Cracking the Coding Interview de Gayle McDowell.
la source
Voici une réponse qui s'exécute avec une mémoire constante, au détriment du CPU. Ce n'est pas une bonne réponse dans le contexte de la question d'origine (c'est-à-dire une réponse lors d'un entretien). Mais si l'interview dure 24 heures, ce n'est pas si mal. ;)
L'idée est que si j'ai n qui est une réponse valide, alors la suivante dans la séquence va être n fois une puissance de deux, divisée par une puissance de 5. Ou bien n fois une puissance de 5, divisée par un puissance de deux. Pourvu qu'il se divise également. (... ou le diviseur peut être 1;) auquel cas vous multipliez simplement par 2 ou 5)
Par exemple, pour passer de 625 à 640, multipliez par 5 ** 4/2 ** 7. Ou, plus généralement, multipliez par une valeur de
2 ** m * 5 ** n
pour m, n où l'on est positif et l'autre négatif ou nul, et le multiplicateur divise le nombre également.Maintenant, la partie délicate est de trouver le multiplicateur. Mais nous savons a) le diviseur doit diviser le nombre également, b) le multiplicateur doit être supérieur à un (les nombres continuent d'augmenter), et c) si nous choisissons le plus petit multiplicateur supérieur à 1 (c'est-à-dire 1 <f <tous les autres f ), alors c'est garanti d'être notre prochaine étape. L'étape qui suivra sera son étape la plus basse.
La partie désagréable est de trouver la valeur de m, n. Il n'y a que des possibilités de log (n), car il n'y a que tant de 2 ou de 5 à abandonner, mais j'ai dû ajouter un facteur de -1 à +1 comme manière bâclée pour gérer l'arrondi. Il suffit donc d'itérer à travers O (log (n)) à chaque étape. C'est donc O (n log (n)) dans l'ensemble.
La bonne nouvelle est que, comme il prend une valeur et trouve la valeur suivante, vous pouvez commencer n'importe où dans la séquence. Donc, si vous voulez le suivant après 1 milliard, il peut simplement le trouver en itérant à travers les 2/5 ou 5/2 et en choisissant le plus petit multiplicateur supérieur à 1.
(python)
J'ai validé les 10 000 premiers numéros générés par rapport aux 10 000 premiers générés par la solution de liste triée, et cela fonctionne au moins jusque-là.
BTW le suivant après un billion semble être 1 024 000 000 000.
...
Hm. Je peux obtenir des performances O (n) - O (1) par valeur (!) - et l'utilisation de la mémoire O (log n) en traitant
best()
comme une table de recherche que j'étends de manière incrémentielle. À l'heure actuelle, il économise de la mémoire en itérant à chaque fois, mais il fait beaucoup de calculs redondants. En maintenant ces valeurs intermédiaires - et une liste de valeurs min - je peux éviter le travail en double et l'accélérer beaucoup. Cependant, la liste des valeurs intermédiaires augmentera avec n, d'où la mémoire O (log n).la source
n
etm
qui ont été utilisés jusqu'à présent dans les numéros de la séquence. À chaque itération,n
oum
peut ou non augmenter. Nous créons un nouveau nombre,2^(max_n+1)*5^(max_m+1)
puis réduisons ce nombre de manière récursive exhaustive à chaque appel, réduisant l'exposant de 1 jusqu'à ce que nous obtenions le minimum plus grand que le nombre actuel. Nous mettons à jourmax_n
,max_m
au besoin. C'est mem constant. Peut êtreO(log^2(n))
mem si le cache DP est utilisé dans l'appel de réductionBrian avait absolument raison - mon autre réponse était bien trop compliquée. Voici un moyen plus simple et plus rapide de le faire.
Imaginez le quadrant I du plan euclidien, limité aux entiers. Appelez un axe l'axe i et l'autre axe l'axe j.
Évidemment, les points proches de l'origine seront choisis avant les points éloignés de l'origine. Notez également que la zone active s'éloignera de l'axe i avant de s'éloigner de l'axe j.
Une fois qu'un point a été utilisé, il ne sera plus jamais utilisé. Et un point ne peut être utilisé que si le point situé juste en dessous ou à gauche a déjà été utilisé.
En les assemblant, vous pouvez imaginer une "frontière" ou un "bord d'attaque" qui commence autour de l'origine et se propage vers le haut et vers la droite, s'étalant le long de l'axe i plus loin que sur l'axe j.
En fait, nous pouvons comprendre quelque chose de plus: il y aura au plus un point sur la frontière / le bord pour une valeur i donnée. (Vous devez incrémenter i plus de 2 fois pour égaler un incrément de j.) Ainsi, nous pouvons représenter la frontière comme une liste contenant un élément pour chaque coordonnée i, ne variant qu'avec la coordonnée j et la valeur de la fonction.
À chaque passage, nous sélectionnons l'élément minimum sur le bord d'attaque, puis le déplaçons une fois dans la direction j. S'il se trouve que nous élevons le dernier élément, nous ajoutons un nouveau dernier élément supplémentaire avec une valeur i incrémentée et une valeur j de 0.
Espace: O (n) en nombre d'éléments imprimés jusqu'à présent.
Vitesse: O (1) inserts, mais ceux-ci ne sont pas effectués à chaque fois. (Parfois plus long quand le
List<>
doit croître, mais toujours O (1) amorti). Le grand puits de temps est la recherche du minimum, O (n) dans le nombre d'éléments imprimés jusqu'à présent.la source
Does anyone have advice on how to solve such a problem?
de tenter de comprendre le problème sous-jacent. Un vidage de code ne répond pas bien à cette question.La solution basée sur l'ensemble était probablement ce que votre enquêteur recherchait, mais elle a la conséquence malheureuse d'avoir de la
O(n)
mémoire et duO(n lg n)
temps total pour lesn
éléments de séquençage .Un peu de mathématiques nous aide à trouver une solution d'
O(1)
espace et deO(n sqrt(n))
temps. Remarquez cela2^i * 5^j = 2^(i + j lg 5)
. Trouver les premiersn
éléments de{i,j > 0 | 2^(i + j lg 5)}
réduit à trouver les premiersn
éléments de{i,j > 0 | i + j lg 5}
parce que la fonction(x -> 2^x)
est strictement monotone croissante, la seule façon pour certainsa,b
qui2^a < 2^b
est sia < b
.Maintenant, nous avons juste besoin d'un algorithme pour trouver la séquence de
i + j lg 5
, oùi,j
sont les nombres naturels. En d'autres termes, étant donné notre valeur actuelle dei, j
, ce qui minimise le prochain mouvement (c'est-à-dire nous donne le nombre suivant dans la séquence), c'est une augmentation de l'une des valeurs (disonsj += 1
) avec une diminution de l'autre (i -= 2
). La seule chose qui nous limite, c'est celai,j > 0
.Il n'y a que deux cas à considérer - des
i
augmentations ou desj
augmentations. L'un d'eux doit augmenter car notre séquence augmente, et les deux n'augmentent pas car sinon nous sautons le terme dans lequel nous n'avons qu'un seul d'i,j
augmentation. Ainsi, l'un augmente et l'autre reste le même ou diminue. Exprimé en C ++ 11, l'ensemble de l'algorithme et sa comparaison avec la solution définie sont disponibles ici .Cela permet d'obtenir une mémoire constante car il n'y a qu'une quantité constante d'objets alloués dans la méthode, en dehors du tableau de sortie (voir lien). La méthode réalise le temps logarithmique à chaque itération car pour tout donné
(i,j)
, elle parcourt pour la meilleure paire(a, b)
telle que(i + a, j + b)
la plus petite augmentation de la valeur dei + j lg 5
. Cette traversée estO(i + j)
:Toutes les tentatives d'itération pour mettre à jour
i
, puisj
, et va de pair avec la plus petite mise à jour des deux.Depuis
i
et toutj
au plusO(sqrt(n))
, nous avons duO(n sqrt(n))
temps total .i
etj
croître au taux du carré den
puisque pour toutes les valeurs maximalesimax
etjmax
il existeO(i j)
des paires uniques à partir desquelles faire notre séquence si notre séquence est desn
termes, eti
etj
croître dans un facteur constant les uns des autres (parce que l'exposant est composé d'un linéaire combinaison des deux), nous le savonsi
et lej
sommesO(sqrt(n))
.Il n'y a pas grand-chose à craindre en ce qui concerne l'erreur en virgule flottante - puisque les termes croissent de façon exponentielle, nous aurions à faire face au débordement avant que l'erreur de flop ne nous rattrape, de plusieurs magnitudes. J'y ajouterai plus de discussion si j'ai le temps.
la source