web-dev-qa-db-ja.com

駅の最寄駅

私はこのインタビューの質問を受けて、それにこだわった。

駅番号0から始まる列車の停留所の数に制限はありません。

列車の数は無限です。 n番目の列車は、すべてのk * 2 ^(n-1)で停止します。ここで、kは0〜無限大です。

N = 1の場合、最初の列車はストップ0、1、2、3、4、5、6などで停止します。

N = 2の場合、2番目の列車はストップ0、2、4、6、8などで停止します。

N = 3の場合、3番目の列車はストップ0、4、8、12などで停止します。

開始ステーション番号と終了ステーション番号を指定して、それらの間のストップの最小数を返します。任意の列車を使用して、ある停留所から別の停留所に移動できます。

たとえば、start = 1とend = 4の間のストップの最小数は3です。これは、1から2から4まで取得できるためです。

dp[start][end]startendの間の最小ステップ数を格納する動的プログラミングソリューションについて考えています。 start...mid1, mid1...mid2, mid2...mid3, ..., midn...endを使用して配列を作成します。しかし、私はそれを機能させることができませんでした。これをどのように解決しますか?

明確化:

  1. 列車は、低い番号のストップから高い番号のストップまでしか前進できません。
  2. 電車は、停車する駅であればどこからでも出発できます。
  3. 列車は任意の順序で乗車できます。 n = 1列車は、n = 3列車に乗車する前または乗車した後に乗車できます。
  4. 列車は複数回乗車できます。たとえば、n = 1列車に乗り、次のn = 2列車に乗り、最後に再びn = 1列車に乗ります。
39
user9292787

この問題には、動的プログラミングはまったく必要ないと思います。基本的にはバイナリ計算で表現できます。

ステーションの番号を2進数に変換すると、ステーション0からそこに到達する方法がすぐにわかります。たとえば、

ステーション6 = 110

1つの駅でそれぞれn = 3列車とn = 2列車を利用する必要があることを伝えます。したがって、バイナリ表現の popcount は、必要なステップ数を示します。

次のステップは、あるステーションから別のステーションへの行き方を理解することです。例でこれをもう一度示します。駅7から駅23まで行きたいとしましょう。

ステーション7 = 00111

ステーション23 = 10111

最初にやりたいことは、途中で停止することです。このストップは

(開始ステーションと終了ステーションで等しい最上位ビット)+(最初の異なるビット)+(ゼロで埋められます)

この例では、中間ストップは16(10000)です。必要なステップは、その番号と開始ステーションの差(7 = 00111)で計算できます。この例では、これにより

10000-00111 = 1001

7〜16を取得するには2駅(n = 1列車とn = 4)が必要であることがわかりました。残りのタスクは16〜23を取得することです。これも対応する違いによって解決できます

10111-10000 = 00111

したがって、16から23に移動するには、さらに3つのストップが必要です(n = 3、n = 2、n = 1)。これにより、2つのバイナリ差分とポップカウントを使用するだけで、合計5つのストップが提供されます。結果のパスは、ビット表現7-> 8-> 16-> 20-> 22-> 23から抽出できます。

編集:

中間停車地をさらに明確にするために、

ステーション5 = 101から

ステーション7 = 111

この場合の中間ストップは110になります。

開始ステーションと終了ステーションが等しい最高ビット= 1

最初の異なるビット= 1

ゼロで埋める= 0

そこに行くには1つのステップ(110-101 = 001)が必要で、そこからもう1つのステップ(111-110 = 001)が必要です.

中間停止について

中間停止の概念は少し不格好ですが、ビット操作を機能させるためのよりエレガントな方法を見つけることができませんでした。中間停止は、開始と終了の間の停止であり、最高レベルのビットスイッチです(そのため、そのままの方法で構築されます)。この点で、最速の列車(開始から終了まで)が運行する停留所です(実際には、そこに停車できるすべての列車)。

エンドステーション(ビット表現)から中間ストップ(ビット表現)を引くことにより、ステーション0から始まる単純なケースに問題を減らすことができます(私の回答の最初の例を参照)。

中間停留所から開始駅を引くことにより、問題を単純なケースに減らすこともできますが、中間停留所から出発駅まで行き、逆方向に相当すると仮定します。

27
SaiBot

最初に、後戻りできるかどうかを尋ねます。できないように聞こえますが、ここに示すように(これは受け取った質問を反映していない可能性があります)、問題はこれらの列車のいずれに対しても明確な方向性を与えません。 (質問を編集して、後戻りできないと言っているようです。)

後戻りできないと仮定すると、戦略は単純です。常に、目的地を追い越さない最大の利用可能な列車を利用します。

あなたがsに停車しており、あなたの現在の場所で停車し、行き過ぎない最大番号の列車がkであるとします。電車kを1回旅行すると、s + 2^(k-1)が停止します。その停留所に到達するためのより速い方法も、その停留所をスキップする方法もありません-番号の小さい列車は列車kの停留所をスキップせず、番号の大きい列車は列車kの停車地の間に停車しませんあなたがそこに着く前に高い番号の列車。したがって、kを訓練することは、あなたの最高の即時の動きです。

この戦略を念頭に置いて、残りの最適化のほとんどは、ルート上のすべてのストップを明確に把握せずにストップの数を計算するための効率的なビット調整トリックの問題です。

23
user2357112

この問題は動的プログラミングを必要としません。

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

トレインスキーマは、2のべき乗のマップです。列車の路線をビット表現として視覚化すると、最下位のビットセットが、停留所間の距離が最長の列車の路線を表していることがわかります。短い距離で線を引くこともできます。

距離を最小化するには、エンドステーションに到達できなくなるまで、可能な限り長い距離の回線を使用します。それが、コードの最下位ビットによる追加です。これを実行すると、上位ビットのいくつかは端末の上位ビットと一致しますが、下位ビットはゼロになります。

その時点で、現在のステーションに設定されていないエンドステーションの最上位ビットの列車に乗るだけです。これは__builtin_popcountのコード。

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

一部の人が指摘しているように、停車地はすべて2のべき乗の倍数であるため、より頻繁に停車する列車は、より急行列車の同じ停留所でも停車します。すべての停車駅は、すべての駅で停車する最初の列車のルートにあります。停車は、2番目の列車のルートから最大1ユニット離れており、2番目の駅ごとに停止します。停留所は3番目の列車から最大3ユニットで、4番目ごとに停車します。

そのため、最後から始めて、時間を遡ってルートをトレースします。最も近い2のべき乗の列車に飛び乗り、できるだけ早く2のべき乗の一番高い列車に切り替え続けます(最下位のセットビットの位置を確認します-なぜ?2のべき乗の倍数を2で割ることができます。つまり、ビットシフトの残りを残さずに右にビットシフトします。 、その間隔が1ストップ後に開始点を見逃さない限り。後者の場合は、逆の切り替えを実行し、次に低い2のべき乗の列車に飛び乗って、その間隔が1ストップ後に開始点を逃さないようになるまで続けます。

2

シンプルなJavaソリューション

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

これは、少しのカウントと配列操作以外は何もしないことがわかります。前のすべての回答と同様に、両方の数値をバイナリに変換し、同じ長さにパディングすることから始める必要があります。 12と38は01100と10110になります。

ステーション12、最下位セットビット(この場合、唯一のビット、2 ^ 2)を見ると、2 ^ 2より大きい間隔のすべての列車はステーション4で停止せず、すべての間隔は以下です2 ^ 2は駅4で停車しますが、区間4の列車と同じ目的地に到着するには複数の停車が必要です。あらゆる状況において、終了値の最大セットビットに到達するまで、現在のステーションの最下位ビットの間隔で列車に乗る必要があります。

駅0010110100にいる場合、シーケンスは次のようになります。

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

ここで、最下位のセットビットよりも小さいすべてのビットを削除して、同じカウントを取得できます。

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

各段階で端をトリミングすると、次のようになります。

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

これは、すべての0ビットを反転するプロセスとしても説明できます。これにより、アルゴリズムの前半に到達します。ゼロが埋め込まれた開始番号の最下位セットビットより大きい未設定ビットをカウントします。開始ステーションが0の場合は1です。

これにより、終点駅よりも短い間隔で最大の列車が到達できる唯一の中間駅に到着するため、この後のすべての列車は前の列車よりも小さくなければなりません。

ここで駅から100101に到達する必要があります。より簡単かつ明白で、目的地に設定された現在の駅番号に設定されていない最大有効ビットに等しい間隔で列車に乗ります。

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

最初の方法と同様に、常に設定される最上位ビットをトリミングしてから、回答の残りの1をカウントできます。したがって、アルゴリズムの2番目の部分は最上位ビットよりも小さいすべてのセット有効ビットをカウントです。

次にパート1とパート2の結果を追加

すべての列車間隔を取得するためにアルゴリズムをわずかに調整します。ここで実行できるようにjavascriptで記述された例があります。

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

[〜#〜] notice [〜#〜]:私の答えの下にある現在のコメントの理由は、最初にこのアルゴリズムを完全に間違って記述したことと、user2357112私の間違いから私を認識しました。そこで、私はそのアルゴリズムを完全に削除し、この質問に対するuser2357112の回答に従って新しいアルゴリズムを作成しました。また、このアルゴリズムにいくつかのコメントを追加して、各行で何が起こるかを明確にしました。

このアルゴリズムはprocedure main(Origin, Dest)から始まり、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