题目:准时抵达会议现场的最小跳过休息次数
描述:
给你一个整数 hoursBefore
,表示你要前往会议所剩下的可用小时数。要想成功抵达会议现场,你必须途经 n
条道路。道路的长度用一个长度为 n
的整数数组 dist
表示,其中 dist[i]
表示第 i
条道路的长度(单位:千米)。另给你一个整数 speed
,表示你在道路上前进的速度(单位:千米每小时)。
当你通过第 i
条路之后,就必须休息并等待,直到 下一个整数小时 才能开始继续通过下一条道路。注意:你不需要在通过最后一条道路后休息,因为那时你已经抵达会议现场。
- 例如,如果你通过一条道路用去
1.4
小时,那你必须停下来等待,到2
小时才可以继续通过下一条道路。如果通过一条道路恰好用去2
小时,就无需等待,可以直接继续。
然而,为了能准时到达,你可以选择 跳过 一些路的休息时间,这意味着你不必等待下一个整数小时。注意,这意味着与不跳过任何休息时间相比,你可能在不同时刻到达接下来的道路。
- 例如,假设通过第
1
条道路用去1.4
小时,且通过第2
条道路用去0.6
小时。跳过第1
条道路的休息时间意味着你将会在恰好2
小时完成通过第2
条道路,且你能够立即开始通过第3
条道路。
返回准时抵达会议现场所需要的 最小跳过次数 ,如果 无法准时参会 ,返回 -1
。
示例 1:
输入:dist = [1,3,2], speed = 4, hoursBefore = 2 输出:1 解释: 不跳过任何休息时间,你将用 (1/4 + 3/4) + (3/4 + 1/4) + (2/4) = 2.5 小时才能抵达会议现场。 可以跳过第 1 次休息时间,共用 ((1/4 + 0) + (3/4 + 0)) + (2/4) = 1.5 小时抵达会议现场。 注意,第 2 次休息时间缩短为 0 ,由于跳过第 1 次休息时间,你是在整数小时处完成通过第 2 条道路。
示例 2:
输入:dist = [7,3,5,5], speed = 2, hoursBefore = 10 输出:2 解释: 不跳过任何休息时间,你将用 (7/2 + 1/2) + (3/2 + 1/2) + (5/2 + 1/2) + (5/2) = 11.5 小时才能抵达会议现场。 可以跳过第 1 次和第 3 次休息时间,共用 ((7/2 + 0) + (3/2 + 0)) + ((5/2 + 0) + (5/2)) = 10 小时抵达会议现场。
示例 3:
输入:dist = [7,3,5,5], speed = 1, hoursBefore = 10 输出:-1 解释:即使跳过所有的休息时间,也无法准时参加会议。
关键词:动态规划
说明:对于这类问题,给定某个数组,我们可以对数组中的任意元素进行操作或者不操作,这时我们很自然的想到了动态规划。例如这道题,题目所说,可以选择 跳过 一些路的休息时间,那也就是意味着,我们有跳过或者不跳过两个选择。
因此构建一个数组dp[n+1][n+1],dp[i][j]表示,第i条道路跳过j次后所用的最短时间。当我们选择跳过时,dp[i][j] = dp[i-1][j-1]+dist[i-1]/speed;当我们选择不跳过时,dp[i][j] = dp[i-1][j]+ceil(dist[i-1]/speed)。两者取最小值。
特别地,当j=0时,只能选择不跳过。当j=i时,只能选择跳过。j不能大于i,否则式子没有意义。
(ceil表示向上取整)
最后我们只需要找到dp[n][i](i<=n)中i的最小值就行了。
const double EPS = 1e-7;
const double Max = 1e20;
int minSkips(int* dist, int distSize, int speed, int hoursBefore) {
double f[distSize+1][distSize+1];
for(int i = 0 ; i <= distSize ; i++){
for(int j = 0 ; j <= distSize ; j++){
f[i][j] = Max;
}
}
f[0][0] = 0;
for(int i = 1 ; i<=distSize ;i++){
for(int j = 0;j<=i ; j++){
if(j!=i){
f[i][j] = fmin(f[i][j],ceil(f[i-1][j] + (double)dist[i-1]/speed-EPS));
}
if(j!=0){
f[i][j] = fmin(f[i][j],(f[i-1][j-1]+(double)dist[i-1]/speed));
}
}
}
for(int i = 0 ; i<= distSize ; i++){
if(f[distSize][i]<hoursBefore+EPS){
return i;
}
}
return -1;
}
特别地,里面有关于浮点数的计算法则,我把官解的解释放在下面。
浮点数运算的细节
这一部分非常重要,希望读者仔细阅读。
根据 IEEE 754 标准,浮点数在计算机中存储的精度是有限的,而本题中我们不可避免的会使用「浮点数运算」以及「向上取整」运算,如果强行忽略产生的计算误差,会得到错误的结果。
举一个简单的例子,假设使用的语言中「向上取整」运算的函数为 ceil\texttt{ceil}ceil,下面的运算:
ceil(8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3)\texttt{ceil(8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3)}
ceil(8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3)
应当是 999,而计算机会给出 101010。这是因为浮点数误差导致:
8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3\texttt{8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3}
8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3
计算出的结果约为:
9.000000000000002\texttt{9.000000000000002}
9.000000000000002
向上取整后会得到 101010,产生了错误的答案。
因此我们引入常量 eps\text{eps}eps 表示一个极小值,例如 10−810^{-8}10
−8
。在进行「向上取整」运算前,我们将待取整的浮点数减去 eps\text{eps}eps 再进行取整,就可以避免上述的误差。
同时,我们需要说明 eps\text{eps}eps 不会引入其它的问题。在本题中速度最大为 10610^610
6
,时间与速度成反比,那么 eps\text{eps}eps 的粒度只需要高于时间的精度 10−610^{-6}10
−6
即可,取 10−710^{-7}10
−7
或 10−810^{-8}10
−8
都是可行的。
最后在比较 f[n][j]≤hoursBeforef[n][j] \leq \textit{hoursBefore}f[n][j]≤hoursBefore 时,我们也需要将左侧减去 eps\text{eps}eps 或将右侧加上 eps\text{eps}eps,再进行比较。
作者:力扣官方题解
链接:https://leetcode.cn/problems/minimum-skips-to-arrive-at-meeting-on-time/solutions/802990/minimum-skips-to-arrive-at-meeting-on-ti-dp7v/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。