user9292787
user9292787

Reputation: 361

Minimum number of train station stops

I received this interview question and got stuck on it:

There are an infinite number of train stops starting from station number 0.

There are an infinite number of trains. The nth train stops at all of the k * 2^(n - 1) stops where k is between 0 and infinity.

When n = 1, the first train stops at stops 0, 1, 2, 3, 4, 5, 6, etc.

When n = 2, the second train stops at stops 0, 2, 4, 6, 8, etc.

When n = 3, the third train stops at stops 0, 4, 8, 12, etc.

Given a start station number and end station number, return the minimum number of stops between them. You can use any of the trains to get from one stop to another stop.

For example, the minimum number of stops between start = 1 and end = 4 is 3 because we can get from 1 to 2 to 4.

I'm thinking about a dynamic programming solution that would store in dp[start][end] the minimum number of steps between start and end. We'd build up the array using start...mid1, mid1...mid2, mid2...mid3, ..., midn...end. But I wasn't able to get it to work. How do you solve this?

Clarifications:

  1. Trains can only move forward from a lower number stop to a higher number stop.
  2. A train can start at any station where it makes a stop at.
  3. Trains can be boarded in any order. The n = 1 train can be boarded before or after boarding the n = 3 train.
  4. Trains can be boarded multiple times. For example, it is permitted to board the n = 1 train, next board the n = 2 train, and finally board the n = 1 train again.

Upvotes: 40

Views: 6801

Answers (8)

SaiBot
SaiBot

Reputation: 3745

I don't think you need dynamic programming at all for this problem. It can basically be expressed by binary calculations.

If you convert the number of a station to binary it tells you right away how to get there from station 0, e.g.,

station 6 = 110

tells you that you need to take the n=3 train and the n=2 train each for one station. So the popcount of the binary representation tells you how many steps you need.

The next step is to figure out how to get from one station to another. I´ll show this again by example. Say you want to get from station 7 to station 23.

station 7 = 00111

station 23 = 10111

The first thing you want to do is to get to an intermediate stop. This stop is specified by

(highest bits that are equal in start and end station) + (first different bit) + (filled up with zeros)

In our example the intermediate stop is 16 (10000). The steps you need to make can be calculated by the difference of that number and the start station (7 = 00111). In our example this yields

10000 - 00111 = 1001

Now you know, that you need 2 stops (n=1 train and n=4) to get from 7 to 16. The remaining task is to get from 16 to 23, again this can be solved by the corresponding difference

10111 - 10000 = 00111

So, you need another 3 stops to go from 16 to 23 (n= 3, n= 2, n= 1). This gives you 5 stops in total, just using two binary differences and the popcount. The resulting path can be extracted from the bit representations 7 -> 8 -> 16 -> 20 -> 22 -> 23

Edit:

For further clarification of the intermediate stop let's assume we want to go from

station 5 = 101 to

station 7 = 111

the intermediate stop in this case will be 110, because

highest bits that are equal in start and end station = 1

first different bit = 1

filled up with zeros = 0

we need one step to go there (110 - 101 = 001) and one more to go from there to the end station (111 - 110 = 001).

About the intermediate stop

The concept of the intermediate stop is a bit clunky but I could not find a more elegant way in order to get the bit operations to work. The intermediate stop is the stop in between start and end where the highest level bit switches (that's why it is constructed the way it is). In this respect it is the stop at which the fastest train (between start and end) operates (actually all trains that you are able to catch stop there).

By subtracting the intermediate stop (bit representation) from the end station (bit representation) you reduce the problem to the simple case starting from station 0 (cf. first example of my answer).

By subtracting the start station from the intermediate stop you also reduce the problem to the simple case, but assume that you go from the intermediate stop to the start station which is equivalent to the other way round.

Upvotes: 28

Bax
Bax

Reputation: 4476

Simple Java solution

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;
}

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

I will attempt to prove my algorithm is optimal.

The algorithm is "take the fastest train that doesn't overshoot your destination".

How many stops this is is a bit tricky.

Encode both stops as binary numbers. I claim that an identical prefix can be neglected; the problem of going from a to b is the same as the problem of going from a+2^n to b+2^n if 2^n > b, as the stops between 2^n and 2^(n+1) are just the stops between 0 and 2^n shifted over.

From this, we can reduce a trip from a to b to guarantee that the high bit of b is set, and the same "high" bit of a is not set.

To solve going from 5 (101) to 7 (111), we merely have to solve going from 1 (01) to 3 (11), then shift our stop numbers up 4 (100).

To go from x to 2^n + y, where y < 2^n (and hence x is), we first want to go to 2^n, because there are no trains that skip over 2^n that do not also skip over 2^n+y < 2^{n+1}.

So any set of stops between x and y must stop at 2^n.

Thus the optimal number of stops from x to 2^n + y is the number of stops from x to 2^n, followed by the number of stops from 2^n to 2^n+y, inclusive (or from 0 to y, which is the same).

The algorithm I propose to get from 0 to y is to start with the high order bit set, and take the train that gets you there, then go on down the list.

Claim: In order to generate a number with k 1s, you must take at least k trains. As proof, if you take a train and it doesn't cause a carry in your stop number, it sets 1 bit. If you take a train and it does cause a carry, the resulting number has at most 1 more set bit than it started with.

To get from x to 2^n is a bit trickier, but can be made simple by tracking the trains you take backwards.

Mapping s_i to s_{2^n-i} and reversing the train steps, any solution for getting from x to 2^n describes a solution for getting from 0 to 2^n-x. And any solution that is optimal for the forward one is optimal for the backward one, and vice versa.

Using the result for getting from 0 to y, we then get that the optimal route from a to b where b highest bit set is 2^n and a does not have that bit set is #b-2^n + #2^n-a, where # means "the number of bits set in the binary representation". And in general, if a and b have a common prefix, simply drop that common prefix.

A local rule that generates the above number of steps is "take the fastest train in your current location that doesn't overshoot your destination".

For the part going from 2^n to 2^n+y we did that explicitly in our proof above. For the part going from x to 2^n this is trickier to see.

First, if the low order bit of x is set, obviously we have to take the first and only train we can take.

Second, imagine x has some collection of unset low-order bits, say m of them. If we played the train game going from x/2^m to 2^(n-m), then scaled the stop numbers by multiplying by 2^m we'd get a solution to going from x to 2^n.

And #(2^n-x)/2^m = #2^n - x. So this "scaled" solution is optimal.

From this, we are always taking the train corresponding to our low-order set bit in this optimal solution. This is the longest range train available, and it doesn't overshoot 2^n.

QED

Upvotes: 6

LightBender
LightBender

Reputation: 4253

We can figure this out doing nothing but a little counting and array manipulation. Like all the previous answers, we need to start by converting both numbers to binary and padding them to the same length. So 12 and 38 become 01100 and 10110.

Looking at station 12, looking at the least significant set bit (in this case the only bit, 2^2) all trains with intervals larger than 2^2 won't stop at station 4, and all with intervals less than or equal to 2^2 will stop at station 4, but will require multiple stops to get to the same destination as the interval 4 train. We in every situation, up until we reach the largest set bit in the end value, we need to take the train with the interval of the least significant bit of the current station.

If we are at station 0010110100, our sequence will be:

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

Here we can eliminate all bits smaller than the lest significant set bit and get the same count.

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

Trimming the ends at each stage, we get this:

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

This could equally be described as the process of flipping all the 0 bits. Which brings us to the first half of the algorithm: Count the unset bits in the zero padded start number greater than the least significant set bit, or 1 if the start station is 0.

This will get us to the only intermediate station reachable by the train with the largest interval smaller than the end station, so all trains after this must be smaller than the previous train.

Now we need to get from station to 100101, it is easier and obvious, take the train with an interval equal to the largest significant bit set in the destination and not set in the current station number.

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

Similar to the first method, we can trim the most significant bit which will always be set, then count the remaining 1's in the answer. So the second part of the algorithm is Count all the set significant bits smaller than the most significant bit

Then Add the result from parts 1 and 2

Adjusting the algorithm slightly to get all the train intervals, here is an example written in javascript so it can be run here.

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>

Upvotes: 1

Masoud Keshavarz
Masoud Keshavarz

Reputation: 2234

NOTICE: Reason for current comments under my answer is that first I wrote this algorithm completely wrong and user2357112 awared me from my mistakes. So I completely removed that algorithm and wrote a new one according to what user2357112 answered to this question. I also added some comments into this algorithm to clarify what happens in each line.

This algorithm starts at procedure main(Origin, Dest) and it simulate our movements toward destination with 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)

}

Upvotes: 0

D Krueger
D Krueger

Reputation: 2476

This problem doesn't require dynamic programming.

Here is a simple implementation of a solution using 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;
}

The train schema is a map of powers-of-two. If you visualize the train lines as a bit representation, you can see that the lowest bit set represents the train line with the longest distance between stops that you can take. You can also take the lines with shorter distances.

To minimize the distance, you want to take the line with the longest distance possible, until that would make the end station unreachable. That's what adding by the lowest-set bit in the code does. Once you do this, some number of the upper bits will agree with the upper bits of the end station, while the lower bits will be zero.

At that point, it's simply a a matter of taking a train for the highest bit in the end station that is not set in the current station. This is optimized as __builtin_popcount in the code.

An example going from 5 to 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

Upvotes: 3

גלעד ברקן
גלעד ברקן

Reputation: 23955

As some have pointed out, since stops are all multiples of powers of 2, trains that stop more frequently also stop at the same stops of the more-express trains. Any stop is on the first train's route, which stops at every station. Any stop is at most 1 unit away from the second train's route, stopping every second station. Any stop is at most 3 units from the third train that stops every fourth station, and so on.

So start at the end and trace your route back in time - hop on the nearest multiple-of-power-of-2 train and keep switching to the highest multiple-of-power-of-2 train you can as soon as possible (check the position of the least significant set bit - why? multiples of powers of 2 can be divided by two, that is bit-shifted right, without leaving a remainder, log 2 times, or as many leading zeros in the bit-representation), as long as its interval wouldn't miss the starting point after one stop. When the latter is the case, perform the reverse switch, hopping on the next lower multiple-of-power-of-2 train and stay on it until its interval wouldn't miss the starting point after one stop, and so on.

Upvotes: 2

user2357112
user2357112

Reputation: 280500

First, ask if you can go backward. It sounds like you can't, but as presented here (which may not reflect the question as you received it), the problem never gives an explicit direction for any of these trains. (I see you've now edited your question to say you can't go backward.)

Assuming you can't go backward, the strategy is simple: always take the highest-numbered available train that doesn't overshoot your destination.

Suppose you're at stop s, and the highest-numbered train that stops at your current location and doesn't overshoot is train k. Traveling once on train k will take you to stop s + 2^(k-1). There is no faster way to get to that stop, and no way to skip that stop - no lower-numbered trains skip any of train k's stops, and no higher-numbered trains stop between train k's stops, so you can't get on a higher-numbered train before you get there. Thus, train k is your best immediate move.

With this strategy in mind, most of the remaining optimization is a matter of efficient bit twiddling tricks to compute the number of stops without explicitly figuring out every stop on the route.

Upvotes: 23

Related Questions