准时抵达会议现场的最小跳过休息次数

这篇博客介绍了如何使用动态规划解决一个关于准时到达会议的问题。给定道路长度、速度和剩余时间,你需要计算出最少跳过多少次休息才能准时参加会议。博客中提供了两种动态规划解决方案,一种直接处理浮点数运算,另一种将所有运算转换为整数运算以避免浮点数误差。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

给你一个整数 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
解释:即使跳过所有的休息时间,也无法准时参加会议。

提示

  • n = = d i s t . l e n g t h n == dist.length n==dist.length
  • 1 < = n < = 1000 1 <= n <= 1000 1<=n<=1000
  • 1 < = d i s t [ i ] < = 1 0 5 1 <= dist[i] <= 10^5 1<=dist[i]<=105
  • 1 < = s p e e d < = 1 0 6 1 <= speed <= 10^6 1<=speed<=106
  • 1 < = h o u r s B e f o r e < = 1 0 7 1 <= hoursBefore <= 10^7 1<=hoursBefore<=107

题解

方法一:动态规划

思路与算法

我们用 f [ i ] [ j ] f[i][j] f[i][j] 表示经过了 d i s t [ 0 ] dist[0] dist[0] d i s t [ i − 1 ] dist[i−1] dist[i1] i i i 段道路,并且跳过了 j j j次的最短用时。

在进行状态转移时,我们考虑最后一段道路 d i s t [ i − 1 ] dist[i−1] dist[i1] 是否选择跳过:

  • 如果没有跳过,那么状态转移方程为:
    f [ i ] [ j ] = ⌈ f [ i − 1 ] [ j ] + d i s t [ i − 1 ] s p e e d ⌉ f[i][j]=\lceil f[i-1][j]+\frac{dist[i-1]}{speed} \rceil f[i][j]=f[i1][j]+speeddist[i1]
    其中 ⌈ x ⌉ \lceil x \rceil x 表示将 x x x 向上取整。对于最后一段道路,我们无需等待到下一个整数小时,但由于题目中给定的 hoursBefore \textit{hoursBefore} hoursBefore是一个整数,因此在状态转移方程中向上取整是不会影响结果的。
  • 如果跳过,那么状态转移方程为:
    f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + d i s t [ i − 1 ] s p e e d f[i][j]=f[i-1][j-1]+\frac{dist[i-1]}{speed} f[i][j]=f[i1][j1]+speeddist[i1]

由于我们到达的时间尽可能早,因此需要选择这两种转移中的较小值,即:
f [ i ] [ j ] = m i n { ⌈ f [ i − 1 ] [ j ] + d i s t [ i − 1 ] s p e e d ⌉ , f [ i − 1 ] [ j − 1 ] + d i s t [ i − 1 ] s p e e d } f[i][j]=min\{\lceil f[i-1][j]+\frac{dist[i-1]}{speed} \rceil,f[i-1][j-1]+\frac{dist[i-1]}{speed}\} f[i][j]=min{f[i1][j]+speeddist[i1],f[i1][j1]+speeddist[i1]}

需要注意的是,当 j = 0 j=0 j=0 时,我们不能通过「跳过」进行转移;当 j = i j=i j=i 时,我们不能通过「没有跳过」进行转移;当 j > i j>i j>i 时,我们无法在 i i i 段道路内跳过超过 i i i 次,对应的状态不合法。

当我们计算完所有状态的值后,我们只需要找到最小的 j j j,使得 f [ n ] [ j ] ≤ hoursBefore f[n][j] \leq \textit{hoursBefore} f[n][j]hoursBefore,这个 j j j 即为最少需要跳过的次数。如果不存在这样的 j j j,那么返回 − 1 −1 1

动态规划的细节
动态规划的边界条件为 f [ 0 ] [ 0 ] = 0 f[0][0] = 0 f[0][0]=0,表示初始(未走过任何道路)时的时间为 0 0 0

由于状态转移方程中的取值为 m i n min min,因此我们可以将除了 f [ 0 ] [ 0 ] f[0][0] f[0][0] 以外所有的状态置为一个极大值 ∞ \infty ,方便进行转移。

浮点数运算的细节
根据 IEEE 754 标准,浮点数在计算机中存储的精度是有限的,而本题中我们不可避免的会使用「浮点数运算」以及「向上取整」运算,如果强行忽略产生的计算误差,会得到错误的结果。
举一个简单的例子,假设使用的语言中「向上取整」运算的函数为 ceil \texttt{ceil} ceil,下面的运算: c e i l ( 8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3 ) ceil(8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3) ceil(8.0+1.0/3+1.0/3+1.0/3)
应当是 9 9 9,而计算机会给出 10 10 10。这是因为浮点数误差导致: 8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3 8.0 + 1.0 / 3 + 1.0 / 3 + 1.0 / 3 8.0+1.0/3+1.0/3+1.0/3
计算出的结果约为: 9.000000000000002 9.000000000000002 9.000000000000002

向上取整后会得到 10 10 10,产生了错误的答案。

因此我们引入常量 eps \text{eps} eps 表示一个极小值,例如 1 0 − 8 10^{-8} 108 。在进行「向上取整」运算前,我们将待取整的浮点数减去 eps \text{eps} eps再进行取整,就可以避免上述的误差。

同时,我们需要说明 eps \text{eps} eps不会引入其它的问题。在本题中速度最大为 1 0 6 10^6 106,时间与速度成反比,那么 e p s eps eps的粒度只需要高于时间的精度 1 0 − 6 10^{-6} 106即可,取 1 0 − 7 10^{-7} 107 1 0 − 8 10^{-8} 108都是可行的。

最后在比较 f [ n ] [ j ] ≤ hoursBefore f[n][j] \leq \textit{hoursBefore} f[n][j]hoursBefore 时,我们也需要将左侧减去 eps \text{eps} eps 或将右侧加上 eps \text{eps} eps,再进行比较。

代码
class Solution:
    def minSkips(self, dist: List[int], speed: int, hoursBefore: int) -> int:
        # 可忽略误差
        EPS = 1e-7
        
        n = len(dist)
        f = [[float("inf")] * (n + 1) for _ in range(n + 1)]
        f[0][0] = 0.
        for i in range(1, n + 1):
            for j in range(i + 1):
                if j != i:
                    f[i][j] = min(f[i][j], ceil(f[i - 1][j] + dist[i - 1] / speed - EPS))
                if j != 0:
                    f[i][j] = min(f[i][j], f[i - 1][j - 1] + dist[i - 1] / speed)
        
        for j in range(n + 1):
            if f[n][j] < hoursBefore + EPS:
                return j
        return -1
复杂度分析
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2),即为存储所有状态需要的空间。

方法二:动态规划 + 将所有运算变为整数运算

思路与算法

我们可以将数组 dist \textit{dist} dist中的道路长度和 hoursBefore \textit{hoursBefore} hoursBefore都乘以 speed \textit{speed} speed。由于方法一的代码中所有除法运算的除数都是 speed \textit{speed} speed,因此这样做可以保证所有的除法运算的结果都是整数,从根本上避免浮点数误差。

但需要注意的是,在题目中我们规定「必须休息并等待,直到下一个整数小时才能开始继续通过下一条道路」,那么当我们将上面的变量都乘以 speed \textit{speed} speed后,规定应当变更为「必须休息并等待,直到下一个 speed \textit{speed} speed的倍数小时才能开始继续通过下一条道路」。其余的逻辑与方法一完全相同。

细节

时间 x x x 的下一个 speed \textit{speed} speed的倍数小时为: ( ⌊ x − 1 s p e e d ⌋ + 1 ) ⋅ s p e e d (\lfloor \frac{x-1}{speed} \rfloor +1) \cdot speed (speedx1+1)speed

其中 ⌊ x ⌋ \lfloor x \rfloor x 表示将 x x x向下取整。

代码
class Solution:
    def minSkips(self, dist: List[int], speed: int, hoursBefore: int) -> int:
        n = len(dist)
        f = [[float("inf")] * (n + 1) for _ in range(n + 1)]
        f[0][0] = 0
        for i in range(1, n + 1):
            for j in range(i + 1):
                if j != i:
                    f[i][j] = min(f[i][j], ((f[i - 1][j] + dist[i - 1] - 1) // speed + 1) * speed)
                if j != 0:
                    f[i][j] = min(f[i][j], f[i - 1][j - 1] + dist[i - 1])
        
        for j in range(n + 1):
            if f[n][j] <= hoursBefore * speed:
                return j
        return -1
复杂度分析
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2),即为存储所有状态需要的空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值