题目来源
我的题解
方法一 动态规划+浮点数精度
参考官方题解。
用 f[i][j]表示经过了 dist[0]到 dist[i−1]的 i 段道路,并且跳过了 j 次的最短用时。
在进行状态转移时,考虑最后一段道路 dist[i−1]是否选择跳过:
- 如果没有跳过,那么状态转移方程为:
f [ i ] [ j ] = ⌈ f [ i − 1 ] [ j ] + dist [ i − 1 ] speed ⌉ f[i][j] = \left\lceil f[i-1][j] + \frac{\textit{dist}[i-1]}{\textit{speed}} \right\rceil f[i][j]=⌈f[i−1][j]+speeddist[i−1]⌉
其中 ⌈x⌉表示将 x向上取整。对于最后一段道路,无需等待到下一个整数小时,但由于题目中给定的 hoursBefore是一个整数,因此在状态转移方程中向上取整是不会影响结果的。- 如果跳过,那么状态转移方程为:
f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + dist [ i − 1 ] speed f[i][j] = f[i-1][j-1] + \frac{\textit{dist}[i-1]}{\textit{speed}} f[i][j]=f[i−1][j−1]+speeddist[i−1]由于到达的时间尽可能早,因此需要选择这两种转移中的较小值,即:
f [ i ] [ j ] = min { ⌈ f [ i − 1 ] [ j ] + dist [ i − 1 ] speed ⌉ , f [ i − 1 ] [ j − 1 ] + dist [ i − 1 ] speed } f[i][j] = \min \left\{ \left\lceil f[i-1][j] + \frac{\textit{dist}[i-1]}{\textit{speed}} \right\rceil, f[i-1][j-1] + \frac{\textit{dist}[i-1]}{\textit{speed}}\right\} f[i][j]=min{⌈f[i−1][j]+speeddist[i−1]⌉,f[i−1][j−1]+speeddist[i−1]}
需要注意的是,当 j=0 时,不能通过「跳过」进行转移;当 j=i 时,不能通过「没有跳过」进行转移;当 j>i 时,无法在 i 段道路内跳过超过 i 次,对应的状态不合法。
当计算完所有状态的值后,只需要找到最小的 j,使得 f[n][j]≤hoursBefore,这个 j即为最少需要跳过的次数。如果不存在这样的 jjj,那么返回 −1。
动态规划的细节
- 动态规划的边界条件为 f[0][0]=0,表示初始(未走过任何道路)时的时间为 0。
由于状态转移方程中的取值为 min,因此可以将除了 f[0][0]以外所有的状态置为一个极大值 ∞ \infty ∞,方便进行转移。
浮点数精度问题
引入常量 eps表示一个极小值。在进行「向上取整」运算前,将待取整的浮点数减去 eps再进行取整,就可以避免上述的误差。
同时,需要说明 eps 不会引入其它的问题。在本题中速度最大为 1 0 6 10^6 106,时间与速度成反比,那么 eps 的粒度只需要高于时间的精度 1 0 − 6 10^{-6} 10−6即可,取 1 0 − 7 10^{-7} 10−7 或 1 0 − 8 10^{-8} 10−8 都是可行的。
最后在比较 f[n][j]≤hoursBefore时,也需要将左侧减去 eps或将右侧加上 eps,再进行比较。
时间复杂度:O( n 2 n^2 n2)
空间复杂度:O( n 2 n^2 n2)
public int minSkips(int[] dist, int speed, int hoursBefore) {
int n=dist.length;
double eps=1e-7;
double[][] dp=new double[n+1][n+1];
for(int i=0;i<=n;i++){
Arrays.fill(dp[i],Double.MAX_VALUE);
}
dp[0][0]=0;
for(int i=1;i<=n;i++){
//跳跃次数
for(int j=0;j<=i;j++){
if(j!=i){
dp[i][j]=Math.min(dp[i][j],Math.ceil(dp[i-1][j]+(double)dist[i-1]/speed-eps));
}
if(j!=0){
dp[i][j]=Math.min(dp[i][j],dp[i-1][j-1]+(double)dist[i-1]/speed);
}
}
}
for(int i=0;i<=n;i++){
if(dp[n][i]<hoursBefore+eps)
return i;
}
return -1;
}
方法二 动态规划+不考虑浮点数精度问题
对所有的除法都乘以speed使其成为整数。将「必须休息并等待,直到下一个整数小时才能开始继续通过下一条道路」,那么当我们将上面的变量都乘以 speed 后,规定应当变更为「必须休息并等待,直到下一个 speed 的倍数小时才能开始继续通过下一条道路」
时间复杂度:O( n 2 n^2 n2)
空间复杂度:O( n 2 n^2 n2)
public int minSkips(int[] dist, int speed, int hoursBefore) {
int n=dist.length;
long[][] dp=new long[n+1][n+1];
for(int i=0;i<=n;i++){
Arrays.fill(dp[i],Long.MAX_VALUE);
}
dp[0][0]=0;
for(int i=1;i<=n;i++){
//跳跃次数
for(int j=0;j<=i;j++){
if(j!=i){
dp[i][j] = Math.min(dp[i][j], ((dp[i - 1][j] + dist[i - 1] - 1) / speed + 1) * speed);
}
if(j!=0){
dp[i][j]=Math.min(dp[i][j],dp[i-1][j-1]+dist[i-1]);
}
}
}
for(int i=0;i<=n;i++){
if(dp[n][i]<=(long)hoursBefore*speed)
return i;
}
return -1;
}
有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~