Минимальное количество остановок на вокзале
Я получил этот вопрос интервью и застрял на нем:
Существует бесконечное число остановок поездов, начиная со станции № 0.
Существует бесконечное число поездов. N-й поезд останавливается на всех остановках k * 2^(n-1), где k находится между 0 и бесконечностью.Когда n = 1, первый поезд останавливается на остановках 0, 1, 2, 3, 4, 5, 6, и т.д.
Когда n = 2, второй поезд останавливается на остановках 0, 2, 4, 6, 8, и т.д.
Когда n = 3, то третий поезд останавливается на остановках 0, 4, 8, 12 и т.д.
Учитывая начальный номер станции и номер конечной станции, верните минимальное число остановок между ними. Вы можете использовать любой из поездов, чтобы добраться от одной остановки до другой остановки.
Например, минимальное число остановок между start = 1 и end = 4 равно 3, потому что мы можем получить от 1 до 2 до 4.
Я думаю о решении динамического программирования, которое будет хранить в dp[start][end]
минимальное число шагов между start
и end
. Мы построим массив, используя start...mid1, mid1...mid2, mid2...mid3, ..., midn...end
. Но я не смог заставить его работать. Как вы решаете эту проблему?
Пояснения:
-
Поезда могут двигаться только вперед от остановки с меньшим числом до остановки с большим числом.
Поезд может стартовать с любой станции, где он делает остановку.
Поезда могут садиться в любом порядке. На поезд n = 1 можно сесть до или после посадки на поезд n = 3.
- поезда могут быть загружены несколько раз. Например, это разрешается сесть на поезд n = 1, затем сесть на поезд n = 2 и, наконец, снова сесть на поезд n = 1.
8 ответов:
Я не думаю, что для этой задачи вообще нужно динамическое программирование. Она может быть выражена в основном двоичными вычислениями.
Если вы преобразуете номер станции в двоичный, он сразу же сообщает вам, как добраться до станции 0, например,
Станция 6 = 110
Говорит вам, что вам нужно взять поезд n=3 и поезд n=2 каждый для одной станции. Таким образом, перекрестная сумма двоичного представления говорит вам, сколько шагов вам нужно.
Следующий шаг это выяснить, как добраться с одной станции на другую. Я покажу это еще раз на примере. Допустим, вы хотите добраться от станции 7 до станции 23.
Станция 7 = 00111
Станция 23 = 10111
Первое, что вы хотите сделать, это добраться до промежуточной остановки. Эта остановка задается параметром
(старшие биты, равные в начальной и конечной станции) + (первый разный бит) + (заполненный нулями)
В нашем примере промежуточная остановка ИС 16 (10000). Шаги, которые вам нужно сделать, можно рассчитать по разнице этого числа и начальной станции (7 = 00111). В нашем примере это дает
Теперь вы знаете, что вам нужно 2 остановки (n=1 поезд и n=4), чтобы добраться от 7 до 16. Оставшаяся задача состоит в том, чтобы получить от 16 до 23, опять же это может быть решено соответствующей разностью10000 - 00111 = 1001
10111 - 10000 = 00111
Итак, вам нужно еще 3 остановки, чтобы перейти от 16 до 23 (n= 3, n= 2, n= 1). Это дает вам 5 остановок в общей сложности, просто используя две двоичные разности и оператор кросс-суммы. Результирующий путь может быть извлечен из битовых представлений 7 -> 8 -> 16 -> 20 -> 22 -> 23
Редактировать:
Для дальнейшего уточнения промежуточной остановки предположим, что мы хотим перейти от
Станция 5 = 101 до
Станция 7 = 111
Промежуточной остановкой в этом случае будет 110, так как
Нам нужно сделать один шаг, чтобы добраться туда (110-101 = 001), и еще один, чтобы добраться оттуда до конечной станции (111-110 = 001).Старшие биты, равные в начальной и конечной станции = 1
Первый другой бит = 1
Заполнено нулями = 0
О промежуточной остановке
Концепция промежуточной остановки немного неуклюжа, но я не мог найти более элегантный способ, чтобы заставить битовые операции работать. Промежуточная остановка-это остановка между началом и концом, где самый высокий бит уровня переключает (вот почему он построен так, как он есть). В этом отношении это остановка, на которой работает самый быстрый поезд (между началом и концом) (фактически все поезда, которые вы можете поймать, останавливаются там).
Вычитая промежуточную остановку (битовое представление) из конечной станции (битовое представление), вы сводите задачу к простому случаю, начиная со станции 0 (ср. первый пример моего ответ).
Вычитая начальную станцию из промежуточной остановки, вы также сводите задачу к простому случаю, но предполагаете, что вы идете от промежуточной остановки к начальной станции, что эквивалентно обратному пути.
Во-первых, спросите, можете ли вы вернуться назад. Это звучит так, как будто вы не можете, но как представлено здесь (что может не отражать вопрос, как вы его получили), проблема никогда не дает явного направления для любого из этих поездов. (Я вижу, теперь вы отредактировали свой вопрос, чтобы сказать, что вы не можете вернуться назад.)
Предполагая, что вы не можете вернуться назад, стратегия проста: всегда берите самый высокий номер доступного поезда, который не превышает Ваш пункт назначения.
Предположим, что вы находитесь на остановке
s
, и поезд с наивысшим номером, который останавливается в вашем текущем местоположении и не перегружается, - это поездk
. Путешествие один раз на поездеk
приведет вас к остановкеs + 2^(k-1)
. Нет более быстрого способа добраться до этой остановки, и нет способа пропустить эту остановку - никакие поезда с более низким номером не пропускают ни одну из остановок поездаk
, и никакие поезда с более высоким номером не останавливаются между остановками поездаk
, поэтому вы не можете сесть на поезд с более высоким номером, прежде чем вы туда доберетесь. Таким образом, поездk
- это ваш лучший немедленный ход.С этой стратегией следует иметь в виду, что большая часть оставшейся оптимизации-это вопрос эффективных битовых трюков для вычисления количества остановок без явного определения каждой остановки на маршруте.
Я попытаюсь доказать, что мой алгоритм является оптимальным.
Алгоритм таков:"садитесь на самый быстрый поезд, который не опережает пункт назначения".Сколько это остановок, немного сложно.
Кодируйте обе остановки как двоичные числа. Я утверждаю, что идентичным префиксом можно пренебречь; проблема перехода от
a
кb
та же, что и проблема перехода отa+2^n
кb+2^n
, если2^n > b
, поскольку остановки между2^n
и2^(n+1)
являются просто остановками между0
и2^n
.]} сдвинулся с места.Из этого мы можем сократить путь от
a
доb
, чтобы гарантировать, что высокий битb
установлен, и тот же самый "высокий" битa
не установлен .Чтобы решить переход от 5 (
Чтобы перейти от101
) к 7 (111
), нам просто нужно решить переход от 1 (01
) к 3 (11
), а затем сдвинуть наши стоп-номера вверх на 4 (100
).x
к2^n + y
, гдеy < 2^n
(и, следовательно,x
), мы сначала хотим перейти к2^n
, потому что нет поездов, которые пропускают2^n
, которые также не пропускают2^n+y < 2^{n+1}
. Таким образом, любой набор остановок междуx
иy
должен остановиться на2^n
. Таким образом, оптимальным числом остановок отx
до2^n + y
является число остановок отx
до2^n
, за которым следует число остановок от2^n
до2^n+y
включительно (или от0
доy
, что одно и то же). Алгоритм, который я предлагаю получить от0
доy
, состоит в том, чтобы начать с набора битов высокого порядка и сесть на поезд, который доставит вас туда, а затем идти дальше по списку.Утверждение: для того чтобы сгенерировать число с
k
1
s, Вы должны взять по крайней мереk
поезда. В качестве доказательства, если вы едете на поезде, и это не вызывает переноса номера остановки, он устанавливает 1 бит. Если вы берете поезд, и он действительно вызывает перенос, результирующее число имеет не более чем на 1 бит больше, чем было начато.Добраться от
x
до2^n
немного сложнее, но можно сделать проще, отслеживая поезда, которые вы берете назад .Сопоставляя
s_i
сs_{2^n-i}
и обращая шаги поезда вспять, любое решение для перехода отx
к2^n
описывает решение для перехода от0
к2^n-x
. И любое решение, которое оптимально для прямого, оптимально для обратного, и наоборот.Используя результат для получения от
Локальное правило, которое генерирует указанное выше число шагов, гласит: "возьмите самый быстрый поезд в вашем текущем местоположении, который не превышает Ваш пункт назначения".0
доy
, мы получаем, что оптимальный маршрут отa
доb
, гдеb
наивысший набор битов равен2^n
иa
не имеет этого набора битов, является #b-2^n
+ #2^n-a
, где#
означает "число битов, установленных в двоичном представлении". И вообще, еслиa
иb
имеют общий префикс, просто отбросьте этот общий префикс.Для части, идущей от
2^n
к2^n+y
, мы сделали это явно в нашем доказательстве выше. Для части, идущей отx
к2^n
, это сложнее. видеть.Во-первых, если младший бит
Во-вторых, представьте себе, чтоx
установлен, очевидно, мы должны взять первый и единственный поезд, который мы можем взять.x
имеет некоторую коллекцию несетевых младших битов, скажемm
из них. Если бы мы играли в игру с поездом, идущим отx/2^m
до2^(n-m)
, а затем масштабировали номера остановок, умножая их на2^m
, мы получили бы решение для перехода отx
к2^n
.И #
(2^n-x)/2^m
= #2^n - x
. Таким образом, это "масштабированное" решение является оптимальным.Исходя из этого, мы всегда берем поезд, соответствующий нашему младшему разряду набора в этом оптимальном решении. Это самый длинный доступный поезд дальности, и он не промахивается
2^n
.QED
Эта задача не требует динамического программирования.
Вот простая реализация решения с использованием 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; }
Чтобы минимизировать расстояние, нужно взять линию с наибольшей длиной расстояние возможно, пока это не сделает конечную станцию недостижимой. Это то, что делает добавление младшим битом в коде. Как только вы сделаете это, некоторое количество верхних битов согласуется с верхними битами конечной станции, в то время как нижние биты будут равны нулю.
В этот момент речь идет просто о том, чтобы взять поезд для самого высокого бита на конечной станции, который не установлен на текущей станции. Это оптимизировано как
__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
Как указывали некоторые, поскольку все остановки кратны степени 2, поезда, которые останавливаются чаще, также останавливаются на тех же остановках более скоростных поездов. Любая остановка находится на пути первого поезда, который останавливается на каждой станции. Любая остановка находится не более чем в 1 единице от маршрута второго поезда, останавливаясь на каждой второй станции. Любая остановка находится не более чем в 3-х единицах от третьего поезда, который останавливается на каждой четвертой станции, и так далее.
Итак, начните с конца и проследите свой маршрут назад во времени-прыгайте дальше ближайший поезд кратности мощности 2 и продолжайте переключаться на самый высокий поезд кратности мощности 2, который вы можете как можно скорее (проверьте положение наименее значимого установленного бита - почему? кратные степени 2 можно разделить на две, то есть сдвинуть бит вправо, не оставляя остатка, логарифмировать 2 раза или столько же ведущих нулей в битовом представлении), до тех пор, пока его интервал не пропустит начальную точку после одной остановки. Когда последнее имеет место, выполните обратный переключатель, прыгайте на следующий более низкий поезд кратной мощности 2 и оставайтесь на нем до тех пор, пока его интервал не пропустит начальную точку после одной остановки, и так далее.
Мы можем выяснить это, не делая ничего, кроме небольшого подсчета и манипуляции массивом. Как и все предыдущие ответы, мы должны начать с преобразования обоих чисел в двоичные и заполнения их одинаковой длины. Таким образом, 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 битов. Это подводит нас к первой половине алгоритма: подсчитайте неназначенные биты в начальном номере с нулевой подкладкой больше, чем наименее значимый бит набора, или 1, если начальная станция равна 0.
Это приведет нас к единственной промежуточной станции, доступной поезду с наибольшим интервалом, меньшим, чем конечная станция, поэтому все поезда после этого должны быть меньше, чем предыдущие поезд. Теперь нам нужно добраться от станции до 100101, это проще и очевиднее, сесть на поезд с интервалом, равным самому большому значащему биту, установленному в пункте назначения, а не установленному в текущем номере станции.Подобно первому методу, мы можем обрезать самый значительный бит, который всегда будет установлен, а затем подсчитать оставшиеся 1 в ответе. Таким образом, вторая часть алгоритма подсчитывает все установленные значимые биты, меньшие, чем наиболее значимые бит1000000000 2^7 1010000000 2^5 1010100000 2^4 1010110000 2^2 1010110100
Затем добавьте результат из частей 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>
Простое решение 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; }
Примечание : причина текущих комментариев под моим ответом заключается в том, что сначала я написал этот алгоритм совершенно неправильно и 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) }