web-dev-qa-db-fra.com

Nombre minimum d'arrêts de gare

J'ai reçu cette question d'entrevue et je suis resté coincé dessus:

Il y a un nombre infini d'arrêts de train à partir du numéro de station 0.

Il y a un nombre infini de trains. Le n-ième train s’arrête à tous les k * 2 ^ (n - 1) où k est compris entre 0 et l’infini.

Lorsque n = 1, le premier train s’arrête aux arrêts 0, 1, 2, 3, 4, 5, 6, etc.

Lorsque n = 2, le deuxième train s’arrête aux arrêts 0, 2, 4, 6, 8, etc.

Lorsque n = 3, le troisième train s’arrête aux arrêts 0, 4, 8, 12, etc.

Étant donné un numéro de station de début et un numéro de station d'arrivée, indiquez le nombre minimal d'arrêts entre eux. Vous pouvez utiliser n’importe quel train pour vous rendre d’un arrêt à l’autre.

Par exemple, le nombre minimum d'arrêts entre début = 1 et fin = 4 est égal à 3 car nous pouvons obtenir de 1 à 2 à 4.

Je pense à une solution de programmation dynamique qui stockerait dans dp[start][end] le nombre minimum d’étapes entre start et end. Nous construirions le tableau en utilisant start...mid1, mid1...mid2, mid2...mid3, ..., midn...end. Mais je n'ai pas réussi à le faire fonctionner. Comment résolvez-vous cela?

Clarifications:

  1. Les trains peuvent uniquement avancer d'un arrêt numéro inférieur à un autre.
  2. Un train peut commencer à n’importe quelle gare où il s’arrête.
  3. Les trains peuvent être embarqués dans n'importe quel ordre. Le train n = 1 peut être embarqué avant ou après le train n = 3.
  4. Les trains peuvent être embarqués plusieurs fois. Par exemple, il est permis de monter à bord du train n = 1, de monter ensuite dans le train n = 2 et enfin de monter à nouveau dans le train n = 1.
39
user9292787

Je ne pense pas que vous ayez besoin d'une programmation dynamique pour résoudre ce problème. Il peut fondamentalement être exprimé par des calculs binaires. 

Si vous convertissez le numéro d’une station en binaire, il vous indique immédiatement comment vous y rendre depuis la station 0, par exemple: 

station 6 = 110

vous indique que vous devez prendre le train n = 3 et le train n = 2 chacun pour une gare. Ainsi, la somme croisée de la représentation binaire vous indique le nombre d’étapes dont vous avez besoin.

La prochaine étape consiste à déterminer comment passer d’une station à l’autre ... Je vais le montrer à nouveau par exemple. Supposons que vous souhaitiez vous rendre des stations 7 à 23.

station 7 = 00111 

station 23 = 10111

La première chose à faire est d'arriver à un arrêt intermédiaire. Cet arrêt est spécifié par 

(bits les plus élevés égaux dans les stations de début et de fin) + (premier bit différent) + (rempli de zéros)

Dans notre exemple, l’arrêt intermédiaire est 16 (10000). Les étapes à suivre peuvent être calculées en fonction de la différence entre ce nombre et la station de départ (7 = 00111). Dans notre exemple, cela donne

10000 - 00111 = 1001

Maintenant, vous savez qu'il vous faut 2 arrêts (n = 1 train et n = 4) pour passer de 7 à 16 . La tâche restante consiste à passer de 16 à 23, ce qui peut être résolu à nouveau par la différence correspondante

10111 - 10000 = 00111

Il faut donc encore 3 arrêts pour passer de 16 à 23 (n = 3, n = 2, n = 1). Cela vous donne 5 arrêts au total, en utilisant simplement deux différences binaires et l'opérateur de somme croisée. Le chemin résultant peut être extrait des représentations de bits 7 -> 8 -> 16 -> 20 -> 22 -> 23

Modifier:  

Pour plus de précisions sur l’arrêt intermédiaire, supposons que nous voulons passer de

station 5 = 101 à 

station 7 = 111

l'arrêt intermédiaire sera dans ce cas 110, car

bits les plus élevés qui sont égaux dans les stations de début et de fin = 1 

premier bit différent = 1 

rempli de zéros = 0

il faut un pas pour y aller (110 - 101 = 001) et un autre pour aller de là au terminal (111 - 110 = 001).

A propos de l'arrêt intermédiaire

Le concept de l’arrêt intermédiaire est un peu maladroit, mais je n’ai pas pu trouver un moyen plus élégant de faire en sorte que les opérations de bits fonctionnent. L’arrêt intermédiaire est l’arrêt entre le début et la fin où le bit de niveau le plus élevé commute (c’est pourquoi il est construit tel quel). À cet égard, il s’agit de l’arrêt du train le plus rapide (entre le début et la fin) (en fait, tous les trains que vous êtes en mesure de prendre à l’arrêt). 

En soustrayant l'arrêt intermédiaire (représentation binaire) de la station d'extrémité (représentation binaire), vous réduisez le problème au cas simple à partir de la station 0 (cf. premier exemple de ma réponse).

En soustrayant la station de départ de l'arrêt intermédiaire, vous réduisez également le problème au cas simple, mais supposons que vous passiez de l'arrêt intermédiaire à la station de départ, ce qui équivaut à l'inverse. 

27
SaiBot

Tout d'abord, demandez si vous pouvez revenir en arrière. On dirait que vous ne pouvez pas, mais comme présenté ici (ce qui peut ne pas refléter la question telle que vous l'avez reçue), le problème ne donne jamais de direction explicite à aucun de ces trains. (Je vois que vous avez modifié votre question pour indiquer que vous ne pouvez pas revenir en arrière.)

En supposant que vous ne puissiez pas revenir en arrière, la stratégie est simple: prenez toujours le train disponible le plus numéroté qui ne dépasse pas votre destination.

Supposons que vous êtes à l’arrêt s et que le train dont le numéro est le plus élevé et qui s’arrête à votre position actuelle sans dépasser le train est le train k. Voyager une fois dans le train k vous amènera à arrêter s + 2^(k-1). Il n’existe pas de moyen plus rapide pour se rendre à cet arrêt, et aucun moyen de sauter cet arrêt: aucun train de faible numéro ne saute un des arrêts de train k et aucun train de numéro supérieur n’arrête entre les arrêts de train k, de sorte que vous ne pouvez pas monter. un train plus numéroté avant d'y arriver. Ainsi, former k est votre meilleur déménagement immédiat.

En gardant à l'esprit cette stratégie, l'optimisation restante consiste pour l'essentiel en astuces de bricolage efficaces pour calculer le nombre d'arrêts sans indiquer explicitement chaque arrêt sur l'itinéraire.

23
user2357112

Je vais essayer de prouver que mon algorithme est optimal.

L'algorithme est "prenez le train le plus rapide qui ne dépasse pas votre destination".

Combien d'arrêts c'est un peu délicat.

Encoder les deux arrêts sous forme de nombres binaires. Je prétends qu'un préfixe identique peut être négligé; le problème de passer de a à b est identique au problème de passer de a+2^n à b+2^n si 2^n > b, car les arrêts entre 2^n et 2^(n+1) ne sont que les arrêts entre 0 et 2^n décalés.

De là, nous pouvons réduire un voyage de a à b pour garantir que le bit haut de b est défini et le même bit "haut" de a est pas défini.

Pour résoudre le passage de 5 (101) à 7 (111), nous devons simplement résoudre le passage de 1 (01) à 3 (11), puis décaler nos numéros d'arrêt de 4 (100).

Pour passer de x à 2^n + y, où y < 2^n (et donc x est), nous voulons d’abord aller à 2^n, car il n’existe aucun train qui saute 2^n qui n’échappe pas aussi 2^n+y < 2^{n+1}.

Ainsi, tout ensemble d'arrêts entre x et y doit s'arrêter à 2^n.

Ainsi, le nombre optimal d'arrêts de x à 2^n + y est le nombre d'arrêts de x à 2^n, suivi du nombre d'arrêts de 2^n à 2^n+y, inclus (ou de 0 à y , qui est le même).

L'algorithme que je propose pour passer de 0 à y consiste à commencer par le jeu de bits de poids fort, puis à prendre le train qui vous amène à la base, puis à parcourir la liste.

Revendication: Pour générer un nombre avec k1s, vous devez prendre au moins des trains k. Pour preuve, si vous prenez un train et que cela n’entraîne pas de retenue dans votre numéro d’arrêt, il prend 1 bit. Si vous prenez un train et que cela entraîne un report, le nombre résultant contient au plus 1 bit de plus que le nombre initial.

Passer de x à 2^n est un peu plus compliqué, mais peut être simplifié en suivant les trains que vous prenez en arrière.

Mappant s_i à s_{2^n-i} et inversant les étapes du train, toute solution pour passer de x à 2^n décrit une solution pour passer de 0 à 2^n-x. Et toute solution optimale pour la suivante est la solution optimale pour l’arrière et inversement.

En utilisant le résultat pour obtenir de 0 à y, nous obtenons alors la route optimale de a à bb le plus grand nombre de bits défini est 2^n et a le bit n'est pas défini sur #b-2^n + #2^n-a, où # signifie "le nombre de bits définis dans la représentation binaire". Et en général, si a et b ont un préfixe commun, supprimez simplement ce préfixe commun.

Une règle locale qui génère le nombre d'étapes ci-dessus est "prenez le train le plus rapide de votre position actuelle qui ne dépasse pas votre destination".

Pour la partie allant de 2^n à 2^n+y, nous l'avons fait explicitement dans notre preuve ci-dessus. Pour la partie allant de x à 2^n, c'est plus difficile à voir.

Premièrement, si le bit de poids faible de x est activé, nous devons évidemment prendre le premier et le seul train que nous pouvons prendre.

Deuxièmement, imaginez que x possède une collection de bits de poids faible non définis, par exemple m. Si nous jouions le jeu de train allant de x/2^m à 2^(n-m), puis réduisions les nombres d'arrêt en multipliant par 2^m, nous aurions une solution pour passer de x à 2^n.

Et #(2^n-x)/2^m = #2^n - x. Donc, cette solution "à l'échelle" est optimale.

De là, nous prenons toujours le train correspondant à notre bit de poids faible dans cette solution optimale. C'est le train à longue portée le plus long disponible et il ne dépasse pas 2^n.

QED

5

Ce problème ne nécessite pas de programmation dynamique.

Voici une implémentation simple d'une solution utilisant GCC:

uint32_t min_stops(uint32_t start, uint32_t end)
{
    uint32_t stops = 0;
    if(start != 0) {
        while(start <= end - (1U << __builtin_ctz(start))) {
            start += 1U << __builtin_ctz(start);
            ++stops;
        }
    }
    stops += __builtin_popcount(end ^ start);
    return stops;
}

Le schéma de train est une carte des puissances de deux. Si vous visualisez les lignes de train comme une représentation de bits, vous pouvez voir que le jeu de bits le plus bas représente la ligne de train avec la plus longue distance entre les arrêts que vous pouvez prendre. Vous pouvez également prendre les lignes avec des distances plus courtes.

Pour minimiser la distance, vous voulez prendre la ligne avec la distance la plus longue possible, jusqu'à ce que cela rende la station finale inaccessible. C'est ce que fait l'ajout du bit le plus bas dans le code. Une fois que vous faites cela, un certain nombre de bits supérieurs seront en accord avec les bits supérieurs de la station d'extrémité, tandis que les bits inférieurs seront nuls.

À ce stade, il s’agit simplement de prendre un train pour le bit le plus élevé dans la station finale qui n’est pas défini dans la station actuelle. Ceci est optimisé en tant que __builtin_popcount dans le code.

Un exemple allant de 5 à 39:

000101 5        // Start
000110 5+1=6
001000 6+2=8
010000 8+8=16
100000 16+16=32 // 32+32 > 39, so start reversing the process
100100 32+4=36  // Optimized with __builtin_popcount in code
100110 36+2=38  // Optimized with __builtin_popcount in code
100111 38+1=39  // Optimized with __builtin_popcount in code
3
D Krueger

Comme certains l'ont fait remarquer, comme les arrêts sont tous des multiples de puissances de 2, les trains qui s'arrêtent plus fréquemment s'arrêtent également aux mêmes arrêts que les trains plus express. Tous les arrêts se trouvent sur le trajet du premier train, qui s’arrête à chaque gare. Tous les arrêts se situent au plus à 1 unité de l’itinéraire du deuxième train et s’arrêtent toutes les deux stations. Tous les arrêts sont situés au maximum à 3 unités du troisième train qui s’arrête toutes les quatre stations, et ainsi de suite.

Alors commencez à la fin et tracez votre route dans le passé - montez dans le train à puissance multiple de 2 la plus proche et continuez de passer au train à puissance multiple de la plus grande puissance possible, dès que possible ( vérifie la position du bit de poids le moins significatif - pourquoi? on peut diviser par deux les puissances multiples de 2, c'est-à-dire que le bit est décalé à droite, sans laisser de reste, journal 2 fois, ou autant de zéros en tête dans la représentation en bits , tant que son intervalle ne manquerait pas le point de départ après un arrêt. Lorsque c'est le cas, effectuez l'inversion de marche en sautant dans le prochain train à puissance multiple de 2 inférieure et restez-le jusqu'à ce que son intervalle ne manque pas le point de départ après un arrêt, etc.

2
גלעד ברקן

Solution Java simple

public static int minimumNumberOfStops(int start, final int end) {
    // I would initialize it with 0 but the example given in the question states :
    // the minimum number of stops between start = 1 and end = 4 is 3 because we can get from 1 to 2 to 4
    int stops = 1;
    while (start < end) {
        start += findClosestPowerOfTwoLessOrEqualThan(end - start);
        stops++;
    }
    return stops;
}

private static int findClosestPowerOfTwoLessOrEqualThan(final int i) {
    if (i > 1) {
        return 2 << (30 - Integer.numberOfLeadingZeros(i));
    }
    return 1;
}
1
Bax

Nous pouvons comprendre cela en ne faisant qu'un peu de comptage et de manipulation de tableaux. Comme toutes les réponses précédentes, nous devons commencer par convertir les deux nombres en nombres binaires et les ajouter à la même longueur. Donc, 12 et 38 deviennent 01100 et 10110.

En regardant à la station 12, en regardant le bit défini le moins significatif (dans ce cas, le seul bit, 2 ^ 2), tous les trains avec des intervalles supérieurs à 2 ^ 2 ne s’arrêteront pas à la station 4, et tous avec des intervalles inférieurs ou égaux à 2 ^ 2 s'arrêtera à la station 4, mais plusieurs escales seront nécessaires pour atteindre la même destination que le train à intervalle 4. Dans chaque situation, nous devons prendre le train avec l'intervalle du bit le moins significatif de la gare actuelle jusqu'à atteindre le plus gros bit défini dans la valeur finale.

Si nous sommes à la station 0010110100, notre séquence sera la suivante:

0010110100  2^2
0010111000  2^3
0011000000  2^6
0100000000  2^7
1000000000

Ici, nous pouvons éliminer tous les bits plus petits que le bit le moins significatif et obtenir le même nombre.

00101101  2^0
00101110  2^1
00110000  2^4
01000000  2^6
10000000

En coupant les extrémités à chaque étape, on obtient ceci:

00101101  2^0
 0010111  2^0
    0011  2^0
      01  2^0
       1

Cela pourrait également être décrit comme le processus de retournement de tous les bits 0. Ce qui nous amène à la première moitié de l’algorithme: Compter les bits non définis dans le numéro de début rempli à zéro supérieur au bit le moins significatif, ou à 1 si la station de départ est 0.

Cela nous mènera à la seule station intermédiaire accessible par le train avec l'intervalle le plus large plus petit que la gare d'extrémité, de sorte que tous les trains suivants doivent être plus petits que le train précédent.

Maintenant, nous devons aller de la gare à 100101, il est plus facile et évident de prendre le train avec un intervalle égal au plus grand bit significatif défini dans la destination et non défini dans le numéro de station actuel.

1000000000 2^7
1010000000 2^5
1010100000 2^4
1010110000 2^2
1010110100

Semblable à la première méthode, nous pouvons couper le bit le plus significatif qui sera toujours défini, puis compter les 1 restants dans la réponse. Donc, la seconde partie de l’algorithme est Compter tous les bits significatifs définis plus petits que le bit le plus significatif

Puis Ajoutez le résultat des parties 1 et 2

En ajustant légèrement l’algorithme pour obtenir tous les intervalles de train, voici un exemple écrit en javascript afin de pouvoir le lancer ici.

function calculateStops(start, end) {
  var result = {
    start: start,
    end: end,
  	count: 0,
  	trains: [],
    reverse: false
  };
  
  // If equal there are 0 stops
  if (start === end) return result;

  // If start is greater than end, reverse the values and
  // add note to reverse the results
  if (start > end) {
    start = result.end;
    end = result.start;
    result.reverse = true;
  }    

  // Convert start and end values to array of binary bits
  // with the exponent matched to the index of the array
  start = (start >>> 0).toString(2).split('').reverse();
  end = (end >>> 0).toString(2).split('').reverse();

  // We can trim off any matching significant digits
  // The stop pattern for 10 to 13 is the same as
  // the stop pattern for 2 to 5 offset by 8
	while (start[end.length-1] === end[end.length-1]) {
    start.pop();
    end.pop();
  }

  // Trim off the most sigificant bit of the end, 
  // we don't need it
  end.pop();

  // Front fill zeros on the starting value
  // to make the counting easier
  while (start.length < end.length) {
    start.Push('0');
  }

  // We can break the algorithm in half
  // getting from the start value to the form
  // 10...0 with only 1 bit set and then getting
  // from that point to the end.

  var index;
	var trains = [];
  var expected = '1';

  // Now we loop through the digits on the end
  // any 1 we find can be added to a temporary array
  for (index in end) {
    if (end[index] === expected){
    	result.count++;
      trains.Push(Math.pow(2, index));
    };
  }

  // if the start value is 0, we can get to the 
  // intermediate step in one trip, so we can
  // just set this to 1, checking both start and
  // end because they can be reversed
  if (result.start == 0 || result.end == 0) {
    index++
    result.count++;
    result.trains.Push(Math.pow(2, index));
  // We need to find the first '1' digit, then all
  // subsequent 0 digits, as these are the ones we
  // need to flip
  } else {
    for (index in start) {
      if (start[index] === expected){
        result.count++;
        result.trains.Push(Math.pow(2, index));
        expected = '0';
      }
    }  
  }

  // add the second set to the first set, reversing
  // it to get them in the right order.
	result.trains = result.trains.concat(trains.reverse());

  // Reverse the stop list if the trip is reversed
	if (result.reverse) result.trains = result.trains.reverse();
  
  return result;
}

$(document).ready(function () {
	$("#submit").click(function () {
  	var trains = calculateStops(
      parseInt($("#start").val()),
      parseInt($("#end").val())
    );

  	$("#out").html(trains.count);
    
  	var current = trains.start;
    var stopDetails = 'Starting at station ' + current + '<br/>';
    for (index in trains.trains) {
      current = trains.reverse ? current - trains.trains[index] : current + trains.trains[index];
      stopDetails = stopDetails + 'Take train with interval ' + trains.trains[index] + ' to station ' + current + '<br/>';
    }

  	$("#stops").html(stopDetails);
  });
});
label {
  display: inline-block;
  width: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label>Start</label> <input id="start" type="number" /> <br>
<label>End</label> <input id="end" type="number" /> <br>
<button id="submit">Submit</button>

<p>Shortest route contains <span id="out">0</span> stops</p>
<p id="stops"></p>

1
LightBender

REMARQUE: La raison des commentaires actuels dans ma réponse est que, d’abord, j’ai écrit cet algorithme complètement faux et que user2357112 m’a fait comprendre mes erreurs. J'ai donc complètement supprimé cet algorithme et en ai écrit un nouveau en fonction de ce que user2357112 a répondu à cette question. J'ai également ajouté quelques commentaires dans cet algorithme pour clarifier ce qui se passe dans chaque ligne.

Cet algorithme commence à procedure main(Origin, Dest) et simule nos mouvements vers la destination avec updateOrigin(Origin, Dest)

procedure main(Origin, Dest){

         //at the end we have number of minimum steps in this variable
         counter = 0;

         while(Origin != Dest){

              //we simulate our movement toward destination with this
              Origin = updateOrigin(Origin, Dest);

              counter = counter + 1;

         }

}

procedure updateOrigin(Origin, Dest){

    if (Origin == 1) return 2;

    //we must find which train pass from our Origin, what comes out from this IF clause is NOT exact choice and we still have to do some calculation in future
    if (Origin == 0){

       //all trains pass from stop 0, thus we can choose our train according to destination
       n = Log2(Dest);

    }else{

       //its a good starting point to check if it pass from our Origin
       n = Log2(Origin);

    }

    //now lets choose exact train which pass from Origin and doesn't overshoot destination
    counter = 0;
    do {
             temp = counter * 2 ^ (n - 1);

             //we have found suitable train
             if (temp == Origin){

                 //where we have moved to
                 return Origin + 2 ^ ( n - 1 );

             //we still don't know if this train pass from our Origin
             } elseif (temp < Origin){

                 counter = counter + 1;

             //lets check another train
             } else {

                 n = n - 1;
                 counter  = 0;

             }

    }while(temp < Origin)

}
0
Masoud Keshavarz