web-dev-qa-db-fra.com

nombre minimum d'étapes pour réduire le nombre à 1

Étant donné n'importe quel nombre n, et trois opérations sur n:

  1. ajouter 1
  2. soustraire 1
  3. divisez par 2 si le nombre est pair

Je veux trouver le nombre minimum des opérations ci-dessus pour réduire n à 1. J'ai essayé l'approche de programmation dynamique, également BFS avec élagage, mais n peut être très grand (10 ^ 300) et je ne sais pas comment faire mon algorithme plus rapide. L'approche gourmande (diviser par 2 si pair et soustraire 1 si impair) ne donne pas non plus le résultat optimal. Existe-t-il une autre solution?

22
natydn52

Il existe un schéma qui vous permet de connaître la prochaine étape optimale en temps constant. En fait, il peut y avoir des cas où il y a deux choix également optimaux - dans ce cas, l'un d'eux peut être dérivé en temps constant.

Si vous regardez la représentation binaire de n , et ses bits les moins significatifs, vous pouvez tirer quelques conclusions sur l'opération conduisant à la solution. En bref:

  • si le bit le moins significatif est nul, divisez par 2
  • si n vaut 3 ou si les 2 bits les moins significatifs sont 01, alors soustrayez
  • Dans tous les autres cas: ajoutez.

Preuve

Si le bit le moins significatif est nul, l'opération suivante devrait être la division par 2. Nous pourrions plutôt essayer 2 additions puis une division, mais ce même résultat peut être obtenu en deux étapes: diviser et ajouter. De même avec 2 soustractions. Et bien sûr, nous pouvons ignorer les étapes inutiles suivantes d'ajout et de soustraction (ou vice versa). Donc, si le dernier bit est 0, la division est la voie à suivre.

Ensuite, les modèles 3 bits restants sont comme **1. Il y en a quatre. Écrivons a011 pour désigner un nombre qui se termine par des bits 011 et possède un ensemble de bits préfixés qui représenteraient la valeur a :

  • a001: en ajouter un donnerait a010, après quoi une division doit se produire: a01: 2 étapes ont été franchies. Nous ne voudrions pas en soustraire un maintenant, car cela conduirait à a00, auquel nous aurions pu arriver en deux étapes depuis le début (soustraire 1 et diviser). Encore une fois, nous ajoutons et divisons pour obtenir a1, et pour la même raison, nous le répétons encore une fois, en donnant: a+1. Cela a pris 6 étapes, mais conduit à un nombre qui pourrait être obtenu en 5 étapes (soustraire 1, diviser 3 fois, ajouter 1), donc clairement, nous ne devons pas effectuer l'addition. La soustraction est toujours meilleure.

  • a111: l'addition est égale ou meilleure que la soustraction. En 4 étapes, nous obtenons a+1. La soustraction et la division donneraient a11. Ajouter maintenant serait inefficace par rapport au chemin d'addition initial, donc nous répétons cette soustraction/division deux fois et obtenons a en 6 étapes. Si a se termine par 0, alors nous aurions pu le faire en 5 étapes (ajouter, diviser trois fois, soustraire), si a se termine par 1, puis même en 4. Donc, l'addition est Toujours mieux.

  • a101: la soustraction et la double division conduisent à a1 en 3 étapes. L'addition et la division conduisent à a11. Pour l'instant, soustraire et diviser serait inefficace par rapport au chemin de soustraction, donc nous ajoutons et divisons deux fois pour obtenir a+1 en 5 étapes. Mais avec le chemin de soustraction, nous pourrions atteindre cela en 4 étapes. La soustraction est donc toujours meilleure.

  • a011: l'addition et la double division conduisent à a1. Pour obtenir a, il faudrait 2 étapes supplémentaires (5), pour obtenir a+1: un de plus (6). La soustraction, la division, la soustraction, la double division conduit à a (5), pour obtenir a+1 ferait un pas de plus (6). Ainsi, l'addition est au moins aussi bonne que la soustraction. Il y a cependant un cas à ne pas négliger: si a vaut 0, alors le chemin de soustraction atteint la solution à mi-chemin, en 2 étapes, tandis que l'addition le chemin prend 3 étapes. Donc l'addition mène toujours à la solution, sauf quand n vaut 3: alors la soustraction doit être choisie.

Ainsi, pour les nombres impairs, l'avant-dernier bit détermine l'étape suivante (sauf pour 3).

Code Python

Cela conduit à l'algorithme suivant (Python), qui nécessite une itération pour chaque étape et devrait donc avoir O (logn) complexité:

def stepCount(n):
    count = 0
    while n > 1:
        if n % 2 == 0:             # bitmask: *0
            n = n // 2
        Elif n == 3 or n % 4 == 1: # bitmask: 01
            n = n - 1
        else:                      # bitmask: 11
            n = n + 1
        count += 1
    return count

Voir exécuter sur repl.it .

Extrait JavaScript

Voici une version où vous pouvez entrer une valeur pour n et laisser l'extrait de code produire le nombre d'étapes:

function stepCount(n) {
    var count = 0
    while (n > 1) {
        if (n % 2 == 0)                // bitmask: *0
            n = n / 2
        else if (n == 3 || n % 4 == 1) // bitmask: 01
            n = n - 1
        else                           // bitmask: 11
            n = n + 1
        count += 1
    }
    return count
}

// I/O
var input = document.getElementById('input')
var output = document.getElementById('output')
var calc = document.getElementById('calc')

calc.onclick = function () {
  var n = +input.value
  if (n > 9007199254740991) { // 2^53-1
    alert('Number too large for JavaScript')
  } else {
    var res = stepCount(n)
    output.textContent = res
  }
}
<input id="input" value="123549811245">
<button id="calc">Caluclate steps</button><br>
Result: <span id="output"></span>

Veuillez noter que la précision de JavaScript est limitée à environ 1016, les résultats seront donc incorrects pour des nombres plus importants. Utilisez plutôt le script Python pour obtenir des résultats précis.

32
trincot

Pour résoudre le problème ci-dessus, vous pouvez utiliser la récursivité ou les boucles Une réponse récursive est déjà fournie, donc j'essaierais de donner une approche en boucle while.

Logique: Nous devons nous rappeler que le nombre multiple de 2 aurait toujours moins de bits définis que ceux qui ne sont pas divisibles par 2.

Pour résoudre votre problème, j'utilise Java. Je l'ai essayé avec quelques chiffres et cela fonctionne bien, s'il n'ajoute pas de commentaire ou ne modifie pas la réponse

while(n!=1)
    {
        steps++;
        if(n%2 == 0)
        {
            n=n/2;

        }
        else
        {
            if(Integer.bitCount(n-1) > Integer.bitCount(n+1))
            {
                n += 1;
            }
            else
            {
                n -=1;
            }
        }
    }

    System.out.println(steps);

Le code est écrit sous une forme très simple afin qu'il puisse être compris par tout le monde. Ici n est le nombre entré et étapes sont les étapes nécessaires pour atteindre 1

1
Prashant Negi

J'aime l'idée par ossifrage délicat de regarder goulûment (pour le cas des nombres impairs) si n + 1 ou n - 1 semble plus prometteur, mais pensez à décider ce qui semble plus prometteur peut être fait un peu mieux que de regarder le nombre total de bits définis.

Pour un nombre x,

bin(x)[:: -1].index('1')

indique le nombre de 0 les moins significatifs jusqu'au premier 1. L'idée est alors de voir si ce nombre est plus élevé pour n + 1 ou n - 1, et choisissez le plus élevé des deux (de nombreux 0 consécutifs les moins significatifs indiquent une réduction de moitié consécutive).

Cela mène à

def min_steps_back(n):
    count_to_1 = lambda x: bin(x)[:: -1].index('1')

    if n in [0, 1]:
        return 1 - n

    if n % 2 == 0:
        return 1 + min_steps_back(n / 2)

    return 1 + (min_steps_back(n + 1) if count_to_1(n + 1) > count_to_1(n - 1) else min_steps_back(n - 1))

Pour comparer les deux, j'ai couru

num = 10000
ms, msb = 0., 0.
for i in range(1000):
    n =  random.randint(1, 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)

    ms += min_steps(n)
    msb += min_steps_back(n)

print ms / num, msb / num

Quelles sorties

57.4797 56.5844

montrant que, en moyenne, cela utilise moins d'opérations (mais pas autant).

1
Ami Tavory

En résumé:

  • Si n est pair, divisez par 2
  • Si n est 3 ou si ses bits les moins significatifs sont 01, soustrayez.
  • Si les bits les moins significatifs de n sont 11, ajoutez.

Répétez ces opérations sur n jusqu'à ce que vous atteigniez 1, en comptant le nombre d'opérations effectuées. Ceci est garanti pour donner la bonne réponse.

Comme alternative à la preuve de @trincot , en voici une qui a moins de cas et qui, espérons-le, est plus claire:

Preuve:

Cas 1: n est pair

Soit y la valeur du nombre après avoir effectué certaines opérations dessus. Pour commencer, y = n.

  1. Supposons que diviser n par 2 n'est pas l'approche optimale.
  2. Ensuite, ajoutez ou soustrayez un nombre pair de fois
    1. Mélanger l'addition et la soustraction entraînera des opérations inutiles, donc l'une ou l'autre n'est effectuée.
    2. Un nombre pair doit être ajouté/soustrait, car l'arrêt d'un nombre impair forcerait la poursuite de l'addition ou de la soustraction.
  3. Soit 2k, où k est un entier, le nombre d'additions ou de soustractions effectuées
    1. Limitez k lors de la soustraction de sorte que n - 2k> = 2.
  4. Après avoir ajouté/soustrait, y = n + 2k, ou y = n - 2k.
  5. Maintenant, divisez. Après la division, y = n/2 + k, ou y = n/2 - k
  6. 2k + 1 opérations ont été effectuées. Mais le même résultat aurait pu être obtenu dans les opérations 1 + k, en divisant d'abord puis en ajoutant ou en soustrayant k fois.
  7. Ainsi, l'hypothèse selon laquelle la division n'est pas l'approche optimale était erronée et la division est l'approche optimale.

Cas 2: n est impair

Le but ici est de montrer que face à un n impair, l'addition ou la soustraction entraînera moins d'opérations pour atteindre un état donné. Nous pouvons utiliser ce fait que la division est optimale face à un nombre pair.

Nous représenterons n avec une chaîne de bits partielle montrant les bits les moins significatifs: X1 ou X01, etc., où X représente les bits restants et est différent de zéro. Lorsque X est 0, les bonnes réponses sont claires: pour 1, vous avez terminé; pour 2 (0b10), divisez; pour 3 (0b11), soustraire et diviser.

Tentative 1: Vérifiez si l'ajout ou la soustraction est préférable avec un bit d'information:

  1. Début: X1
    1. ajouter: (X + 1) 0, diviser: X + 1 (2 opérations)
    2. soustraire: X0, diviser: X (2 opérations)

On arrive à un impasse: si X ou X + 1 étaient pairs, le mouvement optimal serait de se diviser. Mais nous ne savons pas si X ou X + 1 sont pairs, nous ne pouvons donc pas continuer.

Tentative 2: Vérifiez si l'ajout ou la soustraction est préférable avec deux bits d'information:

  1. Début: X01
    1. ajouter: X10, diviser: X1
      1. ajouter: (X + 1) 0, diviser: X + 1 (4 opérations)
      2. soustraire: X0, diviser: X (4 opérations)
    2. soustraire: X00, diviser: X0, diviser: X (3 opérations)
      1. ajouter: X + 1 (peut-être pas optimal) (4 opérations)

Conclusion: pour X01, la soustraction entraînera au moins aussi peu d'opérations que l'ajout: 3 et 4 opérations contre 4 et 4 opérations pour atteindre X et X + 1.

  1. Début: X11
    1. ajouter: (X + 1) 00, diviser: (X + 1) 0, diviser: X + 1 (3 opérations)
      1. soustraire: X (peut-être pas optimal) (4 opérations)
    2. soustraire: X10, diviser: X1
      1. ajouter: (X + 1) 0, diviser: X + 1 (4 opérations)
      2. soustraire: X0, diviser: X (4 opérations)

Conclusion: pour X11, l'ajout entraînera au moins aussi peu d'opérations que la soustraction: 3 et 4 opérations contre 4 et 4 opérations pour atteindre X + 1 et X.

Ainsi, si les bits les moins significatifs de n sont 01, soustrayez. Si les bits les moins significatifs de n sont 11, ajoutez.

0
Jay Schauer

La solution proposée par AMI Tavoy fonctionne si le 3 est considéré (l'ajout de 4 produirait 0b100 et count_to_1 est égal à 2, ce qui est supérieur à la soustraction à 2 pour 0b10 et count_to_1 est égal à 1). Vous pouvez ajouter deux étapes lorsque nous descendons non n = 3 pour terminer la solution:

def min_steps_back(n):
count_to_1 = lambda x: bin(x)[:: -1].index('1')

if n in [0, 1]:
    return 1 - n

if n == 3:
    return 2

if n % 2 == 0:
    return 1 + min_steps_back(n / 2)

return 1 + (min_steps_back(n + 1) if count_to_1(n + 1) > count_to_1(n - 1) else min_steps_back(n - 1))

Désolé, je sais que cela ferait un meilleur commentaire, mais je viens de commencer.

0
bjdduck