我收到了这个面试问题并且坚持了下来:
从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
构建数组。但我无法让它发挥作用。你是怎么解决的?
澄清:
答案 0 :(得分:27)
我认为你根本不需要动态编程来解决这个问题。它基本上可以用二进制计算表示。
如果您将电台号码转换为二进制,它会立即告诉您如何从电台0到达,例如
站6 = 110
告诉您需要将n = 3列车和n = 2列车分别用于一个站点。因此,二进制表示的交叉总和会告诉您需要多少步骤。
下一步是弄清楚如何从一个站到另一个站。 我会再举例说明这一点。假设您想从7号站到23号站。
第7站= 00111
第23站= 10111
你要做的第一件事是进入中间站。此停靠由
指定(起始站和终点站相同的最高位)+(第一个不同的位)+(用零填充)
在我们的例子中,中间停止是16(10000)。您需要进行的步骤可以通过该数字与起始站的差值来计算(7 = 00111)。在我们的例子中,这产生了
10000 - 00111 = 1001
现在你知道,你需要2站(n = 1列车和n = 4)从7到16。 剩下的任务是从16到23,再次这可以通过相应的差异来解决
10111 - 10000 = 00111
所以,你需要另外3站从16到23(n = 3,n = 2,n = 1)。这总共给出了5个停靠点,只使用了两个二进制差值和交叉求和运算符。可以从比特表示7->提取所得到的路径。 8 - > 16 - > 20 - > 22 - > 23
修改:
为了进一步澄清中间停止,我们假设我们想要从
开始站5 = 101到
第7站= 111
这种情况下的中间停止是110,因为
起始站和终点站中相等的最高位= 1
第一个不同的位= 1
用零填充= 0
我们需要一步到那里(110 - 101 = 001),还有一步从那里到终点站(111 - 110 = 001)。
关于中间停止
中间停止的概念有点笨拙,但我找不到更优雅的方式来使位操作起作用。中间停止是在开始和结束之间的停止,其中最高级别位开关(这就是为什么它以它的方式构造)。在这方面,它是最快的列车(开始和结束之间)运行的停靠点(实际上所有能够捕获的列车都停在那里)。
通过从终端站(位表示)中减去中间停止(位表示),您可以将问题简化为从站0开始的简单情况(参见我的答案的第一个示例)。
通过从中间站点减去起始站点,您也可以将问题简化为简单情况,但假设您从中间站点开始到相当于其他方向的起始站点。
答案 1 :(得分:23)
首先,问你是否可以倒退。这听起来像你不能,但正如这里所呈现的那样(可能没有反映出你收到的问题),这个问题从未给出任何这些列车的明确方向。 (我看到你现在编辑了你的问题,说你不能倒退。)
假设您不能倒退,策略很简单:始终使用编号最高的可用列车,但不会超出您的目的地。
假设您已经停在def find_coeff(x0, y0, s0):
def dI(t):
return sqrt(1 + t*t)
def I(t):
rt = sqrt(1 + t*t)
return 0.5 * (t * rt + log(t + rt))
def s(a):
u = y0/x0 + a*x0
l = y0/x0 - a*x0
return 0.5 * (I(u) - I(l)) / a
def ds(a):
u = y0/x0 + a*x0
l = y0/x0 - a*x0
return 0.5 * (a*x0 * (dI(u) + dI(l)) + I(l) - I(u)) / (a*a)
N = 1000
EPSILON = 1e-10
guess = y0 / x0
for i in range(N):
dguess = (s(guess) - s0) / ds(guess)
guess -= dguess
if abs(dguess) <= EPSILON:
print("Break:", abs((s(guess) - s0)))
break
print(i+1, ":", guess)
a = guess
b = y0/x0 - a*x0
print(a, b, s(a))
,并且在当前位置停留并且没有超调的最高编号列车是列车s
。在火车k
上行驶一次将带您停止k
。没有更快的方法可以到达那个站点,也没有办法跳过那个站点 - 没有低号火车跳过任何列车s + 2^(k-1)
的站点,没有更高编号的列车在列车之间停靠{ {1}}停止,所以在到达之前你不能乘坐更高编号的火车。因此,火车k
是您最好的直接行动。
考虑到这一策略,剩下的大部分优化都是通过有效的比特来计算停靠次数而无需明确确定路线上的每个停靠点。
答案 2 :(得分:5)
我将尝试证明我的算法是最佳的。
算法是“乘坐不会超过目的地的最快火车”。
这有多少停止有点棘手。
将两个停靠点编码为二进制数。我声称可以忽略相同的前缀;从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
k
s的号码,您必须至少使用x
列车。作为证明,如果你坐火车并且它不会导致你的停止号码,它会设置1位。如果您乘坐火车并确实导致进位,则结果数字最多比开始时多1个。
从2^n
到s_i
的过程有点棘手,但可以通过跟踪向后的列车来简化。
将s_{2^n-i}
映射到x
并撤销列车步骤,从2^n
到0
的任何解决方案都描述了从2^n-x
到{{}的解决方案{1}}。对于前向解决方案而言,任何最佳解决方案对于后向解决方案都是最佳的,反之亦然。
使用从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
到{{}的解决方案1}}。
而#2^n
=#(2^n-x)/2^m
。因此,这种“缩放”解决方案是最佳的。
由此,我们总是在这个最优解决方案中使用与我们的低阶设置位相对应的列车。这是可用的最长列车,并没有超出2^n - x
。
QED
答案 3 :(得分:3)
这个问题不需要动态编程。
以下是使用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
答案 4 :(得分:2)
正如一些人已经指出的那样,由于停站都是2的倍数,所以更频繁停靠的列车也会停在更快速列车的同一站点。任何站点都在第一列火车的路线上,在每个车站都停靠。任何站点距离第二列火车的路线最多1个单位,停在每个第二站。任何停靠距离第三列火车最多3个单位,每个第四站停靠,依此类推。
所以从最后开始,追溯你的路线 - 跳上最近的2次幂列车,并尽快切换到最高的2次幂列车尽可能(检查最低有效设置位的位置 - 为什么?2的幂的倍数可以除以2,即向右移位,不留余数,记录2次,或者在位中有多少前导零 - 代表),只要其间隔在一次停止后不会错过起点。当后者是这种情况时,执行倒车开关,跳到下一个较低的2次幂列车并继续行驶,直到其间隔在一次停止后不会错过起点,依此类推
答案 5 :(得分:1)
除了一点点计数和数组操作之外,我们可以解决这个问题。与之前的所有答案一样,我们需要首先将两个数字转换为二进制并将它们填充到相同的长度。所以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位。所以算法的第二部分是计算所有设置的有效位小于最高位
然后添加第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>
答案 6 :(得分:1)
简单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;
}
答案 7 :(得分:0)
通知:根据我的回答,当前评论的原因是我首先写错了这个算法并且 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)
}