web-dev-qa-db-fra.com

nième numéro moche

Les nombres dont les seuls facteurs premiers sont 2, 3 ou 5 sont appelés nombres laids.

Exemple:

1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, ...

1 peut être considéré comme 2 ^ 0.

Je travaille sur la recherche du nième numéro moche. Notez que ces nombres sont extrêmement dispersés lorsque n devient grand.

J'ai écrit un programme trivial qui calcule si un nombre donné est moche ou non. Pour n> 500 - c'est devenu super lent. J'ai essayé d'utiliser la mémorisation - observation: ugly_number * 2, ugly_number * 3, ugly_number * 5 sont tous laids. Même avec ça, c'est lent. J'ai essayé d'utiliser certaines propriétés de log - car cela réduira ce problème de la multiplication à l'addition - mais, pas encore beaucoup de chance. Pensé à partager cela avec vous tous. Des idées intéressantes?

Utiliser un concept similaire à "Tamis d'Ératosthène" (merci Anon)

    for (int i(2), uglyCount(0); ; i++) {
            if (i % 2 == 0)
                    continue;
            if (i % 3 == 0)
                    continue;
            if (i % 5 == 0)
                    continue;
            uglyCount++;
            if (uglyCount == n - 1)
                    break;
    }

i est le nième nombre moche.

Même cela est assez lent. J'essaie de trouver le 1500e numéro laid.

41
Anil Katti

Une solution simple et rapide en Java. Utilise l'approche décrite par Anon. .
Ici TreeSet est juste un conteneur capable de renvoyer le plus petit élément dedans. (Aucun doublon stocké.)

    int n = 20;
    SortedSet<Long> next = new TreeSet<Long>();
    next.add((long) 1);

    long cur = 0;
    for (int i = 0; i < n; ++i) {
        cur = next.first();
        System.out.println("number " + (i + 1) + ":   " + cur);

        next.add(cur * 2);
        next.add(cur * 3);
        next.add(cur * 5);
        next.remove(cur);
    }

Puisque le 1000ème numéro laid est 51200000, les stocker dans bool[] N'est pas vraiment une option.

modifier
Comme une récréation du travail (débogage stupide Hibernate), voici une solution complètement linéaire. Merci à marcog pour l'idée!

    int n = 1000;

    int last2 = 0;
    int last3 = 0;
    int last5 = 0;

    long[] result = new long[n];
    result[0] = 1;
    for (int i = 1; i < n; ++i) {
        long prev = result[i - 1];

        while (result[last2] * 2 <= prev) {
            ++last2;
        }
        while (result[last3] * 3 <= prev) {
            ++last3;
        }
        while (result[last5] * 5 <= prev) {
            ++last5;
        }

        long candidate1 = result[last2] * 2;
        long candidate2 = result[last3] * 3;
        long candidate3 = result[last5] * 5;

        result[i] = Math.min(candidate1, Math.min(candidate2, candidate3));
    }

    System.out.println(result[n - 1]);

L'idée est que pour calculer a[i], Nous pouvons utiliser a[j]*2 Pour certains j < i. Mais nous devons également nous assurer que 1) a[j]*2 > a[i - 1] Et 2) j est le plus petit possible.
Ensuite, a[i] = min(a[j]*2, a[k]*3, a[t]*5).

40
Nikita Rybak

Je travaille sur la recherche du nième numéro moche. Notez que ces nombres sont extrêmement dispersés lorsque n devient grand.

J'ai écrit un programme trivial qui calcule si un nombre donné est moche ou non.

Cela ressemble à la mauvaise approche pour le problème que vous essayez de résoudre - c'est un peu un algorithme shlemiel.

Connaissez-vous l'algorithme Sieve of Eratosthenes pour trouver des nombres premiers? Quelque chose de similaire (exploiter la connaissance que chaque nombre laid est 2, 3 ou 5 fois un autre nombre laid) fonctionnerait probablement mieux pour résoudre ce problème.

Avec la comparaison avec le tamis, je ne veux pas dire "garder un tableau de bools et éliminer les possibilités au fur et à mesure". Je fais plutôt référence à la méthode générale de génération de solutions basée sur les résultats précédents. Lorsque le tamis obtient un nombre, puis en supprime tous les multiples de l'ensemble candidat, un bon algorithme pour ce problème commencerait par un ensemble vide, puis ajouter les multiples corrects de chaque nombre laid à cela.

11
Anon.

Ma réponse fait référence à la bonne réponse donnée par Nikita Rybak. Pour que l'on puisse voir une transition de l'idée de la première approche à celle de la seconde.

from collections import deque
def hamming():
    h=1;next2,next3,next5=deque([]),deque([]),deque([])
    while True:
        yield h
        next2.append(2*h)
        next3.append(3*h)
        next5.append(5*h)
        h=min(next2[0],next3[0],next5[0])
        if h == next2[0]: next2.popleft()
        if h == next3[0]: next3.popleft()
        if h == next5[0]: next5.popleft()

Ce qui a changé par rapport à la première approche de Nikita Rybak, c'est qu'au lieu d'ajouter les candidats suivants dans une structure de données unique, c'est-à-dire un ensemble d'arbres, on peut ajouter chacun d'eux séparément dans 3 FIFO listes. De cette façon, chaque liste sera toujours trié, et le candidat le moins suivant doit toujours être à la tête d'une ou plusieurs de ces listes.

Si nous éliminons l'utilisation des trois listes ci-dessus, nous arrivons à la deuxième implémentation dans la réponse Nikita Rybak. Cela se fait en évaluant ces candidats (qui doivent figurer dans trois listes) uniquement lorsque cela est nécessaire, de sorte qu'il n'est pas nécessaire de les stocker.

En termes simples:

Dans la première approche, nous mettons chaque nouveau candidat dans une structure de données unique, et c'est mauvais parce que trop de choses se mélangent imprudemment. Cette mauvaise stratégie entraîne inévitablement une complexité temporelle O (log (taille d'arbre)) à chaque fois que nous faisons une requête à la structure. En les mettant dans des files d'attente distinctes, cependant, vous verrez que chaque requête ne prend que O(1) et c'est pourquoi les performances globales se réduisent à O (n) !!! C'est parce que chacun des les trois listes sont déjà triées par elles-mêmes.

8
chanp

Je crois que vous pouvez résoudre ce problème en temps sub-linéaire, probablement O (n ^ {2/3}).

Pour vous donner une idée, si vous simplifiez le problème pour autoriser des facteurs de 2 et 3 seulement, vous pouvez obtenir un temps O (n ^ {1/2}) en commençant par la recherche de la plus petite puissance de deux qui soit au moins aussi grande que le nième nombre laid, puis générer une liste de O (n ^ {1/2}) candidats. Ce code devrait vous donner une idée de comment le faire. Elle repose sur le fait que le nième nombre ne contenant que des puissances de 2 et 3 a une factorisation première dont la somme des exposants est O (n ^ {1/2}).

def foo(n):
  p2 = 1  # current power of 2
  p3 = 1  # current power of 3
  e3 = 0  # exponent of current power of 3
  t = 1   # number less than or equal to the current power of 2
  while t < n:
    p2 *= 2
    if p3 * 3 < p2:
      p3 *= 3
      e3 += 1
    t += 1 + e3
  candidates = [p2]
  c = p2
  for i in range(e3):
    c /= 2
    c *= 3
    if c > p2: c /= 2
    candidates.append(c)
  return sorted(candidates)[n - (t - len(candidates))]

La même idée devrait fonctionner pour trois facteurs autorisés, mais le code devient plus complexe. La somme des puissances de la factorisation tombe à O (n ^ {1/3}), mais vous devez considérer plus de candidats, O (n ^ {2/3}) pour être plus précis.

6
jonderry

Fondamentalement, la recherche pourrait être effectuée O (n):

Considérez que vous conservez un historique partiel des nombres laids. Maintenant, à chaque étape, vous devez trouver la suivante. Il doit être égal à un nombre de l'historique multiplié par 2, 3 ou 5. Choisissez le plus petit d'entre eux, ajoutez-le à l'historique et supprimez-en certains afin que le plus petit de la liste multiplié par 5 soit plus grand que le le plus grand.

Ce sera rapide, car la recherche du prochain numéro sera simple:
min (le plus grand * 2, le plus petit * 5, un à partir du milieu * 3),
supérieur au plus grand nombre de la liste. S'ils sont rares, la liste contiendra toujours peu de chiffres, donc la recherche du nombre à multiplier par 3 sera rapide.

4
ruslik

Pour trouver le nième nombre laid dans O (n ^ (2/3)), l'algorithme de jonderry fonctionnera très bien. Notez que les nombres impliqués sont énorme donc tout algorithme essayant de vérifier si un nombre est laid ou non n'a aucune chance.

La recherche de tous les n plus petits nombres laids dans l'ordre croissant se fait facilement en utilisant une file d'attente prioritaire en temps O (n log n) et en espace O (n): Créez d'abord une file d'attente prioritaire de nombres avec les plus petits nombres, en incluant initialement juste le numéro 1. Puis répétez n fois: Supprimez le plus petit nombre x de la file d'attente prioritaire. Si x n'a pas été supprimé auparavant, alors x est le prochain plus grand nombre laid, et nous ajoutons 2x, 3x et 5x à la file d'attente prioritaire. (Si quelqu'un ne connaît pas le terme file d'attente prioritaire, c'est comme le tas dans l'algorithme heapsort). Voici le début de l'algorithme:

1 -> 2 3 5
1 2 -> 3 4 5 6 10
1 2 3 -> 4 5 6 6 9 10 15
1 2 3 4 -> 5 6 6 8 9 10 12 15 20
1 2 3 4 5 -> 6 6 8 9 10 10 12 15 15 20 25
1 2 3 4 5 6 -> 6 8 9 10 10 12 12 15 15 18 20 25 30
1 2 3 4 5 6 -> 8 9 10 10 12 12 15 15 18 20 25 30
1 2 3 4 5 6 8 -> 9 10 10 12 12 15 15 16 18 20 24 25 30 40

Preuve du temps d'exécution: Nous extrayons un nombre laid de la file d'attente n fois. Nous avons initialement un élément dans la file d'attente, et après avoir extrait un nombre laid, nous ajoutons trois éléments, en augmentant le nombre de 2. Donc, après avoir trouvé n nombres laid, nous avons au plus 2n + 1 éléments dans la file d'attente. L'extraction d'un élément peut se faire en temps logarithmique. Nous extrayons plus de nombres que les nombres laids mais au plus n nombres laids plus 2n - 1 autres nombres (ceux qui auraient pu être dans le tamis après n-1 étapes). Ainsi, le temps total est inférieur à 3n suppressions d'éléments en temps logarithmique = O (n log n), et l'espace total est au maximum de 2n + 1 éléments = O (n).

2
gnasher729

Voici une solution correcte en ML. La fonction ugly () renverra un flux (liste paresseuse) de nombres parasites. La fonction nth peut être utilisée sur ce flux.

Cela utilise la méthode Sieve, les éléments suivants ne sont calculés qu'en cas de besoin.

datatype stream = Item of int * (unit->stream);
fun cons (x,xs) = Item(x, xs);
fun head (Item(i,xf)) = i;
fun tail (Item(i,xf)) = xf();
fun maps f xs = cons(f (head xs), fn()=> maps f (tail xs));

fun nth(s,1)=head(s)
  | nth(s,n)=nth(tail(s),n-1);

fun merge(xs,ys)=if (head xs=head ys) then
                   cons(head xs,fn()=>merge(tail xs,tail ys))
                 else if (head xs<head ys) then
                   cons(head xs,fn()=>merge(tail xs,ys))
                 else
                   cons(head ys,fn()=>merge(xs,tail ys));

fun double n=n*2;
fun triple n=n*3;

fun ij()=
    cons(1,fn()=>
      merge(maps double (ij()),maps triple (ij())));

fun quint n=n*5;

fun ugly()=
    cons(1,fn()=>
      merge((tail (ij())),maps quint (ugly())));

C'était le travail de CS pour la première année :-)

2
fredley

Beaucoup de bonnes réponses ici, mais j'avais du mal à les comprendre, en particulier comment l'une de ces réponses, y compris celle acceptée, maintenait l'axiome 2 dans article original de Dijkstra :

Axiome 2. Si x est dans la séquence, il en est de même de 2 * x, 3 * x et 5 * x.

Après quelques tableaux blancs, il est devenu clair que l'axiome 2 n'est pas un invariant à chaque itération de l'algorithme, mais en fait le but de l'algorithme lui-même. À chaque itération, nous essayons de restaurer la condition dans l'axiome 2. Si last est la dernière valeur de la séquence de résultats S, l'axiome 2 peut simplement être reformulé comme suit:

Pour certains x dans S, la valeur suivante dans S est le minimum de 2x, 3x, et 5x, supérieur à last. Appelons cet axiome 2 '.

Ainsi, si nous pouvons trouver x, nous pouvons calculer le minimum de 2x, 3x, et 5x en temps constant, et ajoutez-le à S.

Mais comment trouver x? Une approche est, nous ne le faisons pas; à la place, chaque fois que nous ajoutons un nouvel élément e à S, nous calculons 2e, 3e, et 5e, et ajoutez-les à une file d'attente de priorité minimale. Puisque ces opérations garantissent que e est dans S, l'extraction simple de l'élément supérieur du PQ satisfait l'axiome 2 '.

Cette approche fonctionne, mais le problème est que nous générons un tas de nombres que nous ne finirons pas par utiliser. Voir this réponse pour un exemple; si l'utilisateur veut le 5ème élément dans S (5), le PQ à ce moment détient 6 6 8 9 10 10 12 15 15 20 25. Ne pouvons-nous pas gaspiller cet espace?

Il s'avère que nous pouvons faire mieux. Au lieu de stocker tous ces nombres, nous maintenons simplement trois compteurs pour chacun des multiples, à savoir 2i, 3j, et 5k. Ce sont des candidats pour le numéro suivant dans S. Lorsque nous en choisissons un, nous n'incrémentons que le compteur correspondant, et non les deux autres. Ce faisant, nous ne générons pas tous les multiples avec impatience, résolvant ainsi le problème spatial avec la première approche.

Voyons un essai à sec pour n = 8, c'est-à-dire le nombre 9. Nous commençons par 1, comme l'indique l'axiome 1 dans l'article de Dijkstra.

+---------+---+---+---+----+----+----+-------------------+
| #       | i | j | k | 2i | 3j | 5k | S                 |
+---------+---+---+---+----+----+----+-------------------+
| initial | 1 | 1 | 1 | 2  | 3  | 5  | {1}               |
+---------+---+---+---+----+----+----+-------------------+
| 1       | 1 | 1 | 1 | 2  | 3  | 5  | {1,2}             |
+---------+---+---+---+----+----+----+-------------------+
| 2       | 2 | 1 | 1 | 4  | 3  | 5  | {1,2,3}           |
+---------+---+---+---+----+----+----+-------------------+
| 3       | 2 | 2 | 1 | 4  | 6  | 5  | {1,2,3,4}         |
+---------+---+---+---+----+----+----+-------------------+
| 4       | 3 | 2 | 1 | 6  | 6  | 5  | {1,2,3,4,5}       |
+---------+---+---+---+----+----+----+-------------------+
| 5       | 3 | 2 | 2 | 6  | 6  | 10 | {1,2,3,4,5,6}     |
+---------+---+---+---+----+----+----+-------------------+
| 6       | 4 | 2 | 2 | 8  | 6  | 10 | {1,2,3,4,5,6}     |
+---------+---+---+---+----+----+----+-------------------+
| 7       | 4 | 3 | 2 | 8  | 9  | 10 | {1,2,3,4,5,6,8}   |
+---------+---+---+---+----+----+----+-------------------+
| 8       | 5 | 3 | 2 | 10 | 9  | 10 | {1,2,3,4,5,6,8,9} |
+---------+---+---+---+----+----+----+-------------------+

Notez que S n'a pas augmenté à l'itération 6, car le candidat minimum 6 avait déjà été ajouté précédemment. Pour éviter ce problème de devoir se souvenir de tous les éléments précédents, nous modifions notre algorithme pour incrémenter tous les compteurs chaque fois que les multiples correspondants sont égaux au candidat minimum. Cela nous amène à l'implémentation suivante Scala.

def hamming(n: Int): Seq[BigInt] = {
  @tailrec
  def next(x: Int, factor: Int, xs: IndexedSeq[BigInt]): Int = {
    val leq = factor * xs(x) <= xs.last
    if (leq) next(x + 1, factor, xs)
    else x
  }

  @tailrec
  def loop(i: Int, j: Int, k: Int, xs: IndexedSeq[BigInt]): IndexedSeq[BigInt] = {
    if (xs.size < n) {
      val a = next(i, 2, xs)
      val b = next(j, 3, xs)
      val c = next(k, 5, xs)
      val m = Seq(2 * xs(a), 3 * xs(b), 5 * xs(c)).min

      val x = a + (if (2 * xs(a) == m) 1 else 0)
      val y = b + (if (3 * xs(b) == m) 1 else 0)
      val z = c + (if (5 * xs(c) == m) 1 else 0)

      loop(x, y, z, xs :+ m)
    } else xs
  }

  loop(0, 0, 0, IndexedSeq(BigInt(1)))
}
2
Abhijit Sarkar

Je suppose que nous pouvons utiliser la programmation dynamique (DP) et calculer nième nombre laid . Une explication complète peut être trouvée à http://www.geeksforgeeks.org/ugly-numbers/

#include <iostream>
#define MAX 1000

using namespace std;

// Find Minimum among three numbers
long int min(long int x, long int y, long int z) {

    if(x<=y) {
        if(x<=z) {
            return x;
        } else {
            return z;
        }
    } else {
        if(y<=z) {
            return y;
        } else {
            return z;
        }
    }   
}


// Actual Method that computes all Ugly Numbers till the required range
long int uglyNumber(int count) {

    long int arr[MAX], val;

    // index of last multiple of 2 --> i2
    // index of last multiple of 3 --> i3
    // index of last multiple of 5 --> i5
    int i2, i3, i5, lastIndex;

    arr[0] = 1;
    i2 = i3 = i5 = 0;
    lastIndex = 1;


    while(lastIndex<=count-1) {

        val = min(2*arr[i2], 3*arr[i3], 5*arr[i5]);

        arr[lastIndex] = val;
        lastIndex++;

        if(val == 2*arr[i2]) {
            i2++;
        }
        if(val == 3*arr[i3]) {
            i3++;
        }
        if(val == 5*arr[i5]) {
            i5++;
        }       
    }

    return arr[lastIndex-1];

}

// Starting point of program
int main() {

    long int num;
    int count;

    cout<<"Which Ugly Number : ";
    cin>>count;

    num = uglyNumber(count);

    cout<<endl<<num;    

    return 0;
}

Nous pouvons voir que c'est assez rapide, il suffit de changer la valeur de [~ # ~] max [~ # ~] pour calculer plus haut Numéro laid

1
ravi_kumar_yadav

voici mon code, l'idée est de diviser le nombre par 2 (jusqu'à ce qu'il donne le reste 0) puis 3 et 5. Si enfin le nombre devient un c'est un chiffre moche. vous pouvez compter et même imprimer tous les nombres laids jusqu'à n.

int count = 0;
for (int i = 2; i <= n; i++) {
    int temp = i;
    while (temp % 2 == 0) temp=temp / 2;
    while (temp % 3 == 0) temp=temp / 3;
    while (temp % 5 == 0) temp=temp / 5;
    if (temp == 1) {
        cout << i << endl;
        count++;
    }

}
0
Nitesh Khandelwal