Medium
题目描述
给你一个浮点数 hour,表示你到达办公室可用的总通勤时间。要通勤到办公室,你必须按顺序乘坐 n 趟火车。另给你一个长度为 n 的整数数组 dist ,其中 dist[i] 表示第 i 趟火车的行驶距离(单位是千米)。
每趟火车都只能在整数小时发车,所以你可能需要在两趟火车之间等待一段时间。
- 例如,第 1 趟火车需要 1.5 小时,那你必须再等待 0.5 小时,搭乘在第 2 小时发车的第 2 趟火车。
返回能准时到达办公室所需的火车最小正整数速度(单位:千米每小时),如果无法准时到达,则返回 -1 。
生成的测试用例保证答案不超过 10^7 ,且 hour 的小数点后最多存在两位数字。
示例 1:
输入:dist = [1,3,2], hour = 6
输出:1
解释:速度为 1 时:
- 第 1 趟火车运行需要 1/1 = 1 小时。
- 由于是在整数时间到达,可以立即换乘在第 1 小时发车的第 2 趟火车。第 2 趟火车运行需要 3/1 = 3 小时。
- 由于是在整数时间到达,可以立即换乘在第 4 小时发车的第 3 趟火车。第 3 趟火车运行需要 2/1 = 2 小时。
- 你将会恰好在第 6 小时到达。
示例 2:
输入:dist = [1,3,2], hour = 2.7
输出:3
解释:速度为 3 时:
- 第 1 趟火车运行需要 1/3 = 0.33333 小时。
- 由于不是在整数时间到达,故需要等待至第 1 小时才能搭乘第 2 趟火车。第 2 趟火车运行需要 3/3 = 1 小时。
- 由于是在整数时间到达,可以立即换乘在第 2 小时发车的第 3 趟火车。第 3 趟火车运行需要 2/3 = 0.66667 小时。
- 你将会在第 2.66667 小时到达。
示例 3:
输入:dist = [1,3,2], hour = 1.9
输出:-1
解释:不可能准时到达,因为第 3 趟火车最早是在第 2 小时发车。
提示:
n == dist.length1 <= n <= 10^51 <= dist[i] <= 10^51 <= hour <= 10^9hour中,小数点后最多只有两位数字
解题思路
这是一个经典的二分搜索问题。关键在于理解题目的约束条件和单调性。
核心思路:
- 时间计算规则:前 n-1 趟火车必须等到整数小时才能发车,最后一趟火车可以在任意时间到达
- 单调性:速度越快,总用时越少,这为二分搜索提供了理论基础
- 边界条件:如果总时间限制小于等于 n-1 小时,则无解(前 n-1 趟火车至少需要 n-1 个整数小时)
算法步骤:
- 首先判断是否有解:
hour <= n-1时返回 -1 - 使用二分搜索在 [1, 10^7] 范围内查找最小速度
- 对于给定速度,计算总用时:
- 前 n-1 趟火车:
ceil(dist[i] / speed)小时(向上取整到整数小时) - 最后一趟火车:
dist[n-1] / speed小时(精确时间)
- 前 n-1 趟火车:
- 如果总用时不超过限制,说明当前速度可行,继续搜索更小的速度
时间复杂度优化:
- 使用
(dist[i] + speed - 1) / speed代替ceil(dist[i] / speed)避免浮点运算 - 二分搜索的右边界设为 10^7(题目保证答案不超过此值)
代码实现
class Solution {
public:
int minSpeedOnTime(vector<int>& dist, double hour) {
int n = dist.size();
if (hour <= n - 1) return -1;
int left = 1, right = 10000000;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (canArrive(dist, hour, mid)) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
private:
bool canArrive(vector<int>& dist, double hour, int speed) {
double totalTime = 0;
int n = dist.size();
for (int i = 0; i < n - 1; i++) {
totalTime += (dist[i] + speed - 1) / speed; // 等价于 ceil(dist[i] / speed)
}
totalTime += (double)dist[n - 1] / speed;
return totalTime <= hour;
}
};
class Solution:
def minSpeedOnTime(self, dist: List[int], hour: float) -> int:
n = len(dist)
if hour <= n - 1:
return -1
left, right = 1, 10**7
result = -1
while left <= right:
mid = (left + right) // 2
if self.canArrive(dist, hour, mid):
result = mid
right = mid - 1
else:
left = mid + 1
return result
def canArrive(self, dist, hour, speed):
total_time = 0
n = len(dist)
for i in range(n - 1):
total_time += (dist[i] + speed - 1) // speed # 等价于 ceil(dist[i] / speed)
total_time += dist[n - 1] / speed
return total_time <= hour
public class Solution {
public int MinSpeedOnTime(int[] dist, double hour) {
int n = dist.Length;
if (hour <= n - 1) return -1;
int left = 1, right = 10000000;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (CanArrive(dist, hour, mid)) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
private bool CanArrive(int[] dist, double hour, int speed) {
double totalTime = 0;
int n = dist.Length;
for (int i = 0; i < n - 1; i++) {
totalTime += (dist[i] + speed - 1) / speed; // 等价于 ceil(dist[i] / speed)
}
totalTime += (double)dist[n - 1] / speed;
return totalTime <= hour;
}
}
var minSpeedOnTime = function(dist, hour) {
const n = dist.length;
if (hour <= n - 1) return -1;
let left = 1, right = 10000000;
let result = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (canArrive(dist, hour, mid)) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
};
function canArrive(dist, hour, speed) {
let totalTime = 0;
const n = dist.length;
for (let i = 0; i < n - 1; i++) {
totalTime += Math.ceil(dist[i] / speed);
}
totalTime += dist[n - 1] / speed;
return totalTime <= hour;
}
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n log(10^7)),其中 n 是火车数量。二分搜索需要 O(log(10^7)) 次迭代,每次迭代需要 O(n) 时间计算总用时 |
| 空间复杂度 | O(1),只使用常数额外空间 |