LeetCode 853 车队(Car Fleet)解题指南:排序 + 栈的 O(n log n) 单调合并思路 📅 发布时间:2026/9/19 2:48:09 👁 浏览次数: LeetCode 853 车队Car Fleet解题指南排序 栈的 O(n log n) 单调合并思路【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode导读本文围绕 LeetCode 853「车队Car Fleet」展开完整拆解该题的最优解法路径——从位置-速度成对建模、按位置降序排序到用栈维护各车到达终点的时间、最终统计车队数量并给出 O(n log n) 时间、O(n) 空间的实现与多语言源码佐证。读完本文你将掌握时间-距离计算 单调栈合并这一类追及/合并问题的通用分析框架。一、问题概述车队是怎么形成的题目描述可概括为在一条单行道上n辆车以不同位置和不同速度驶向同一个目标位置target。一辆车不能超车一旦后车追上或同时到达前车就会以前车的速度并入同一个车队fleet继续行驶。要求返回到达目标时总共会形成多少个车队。仓库中 C 实现 cpp/0853-car-fleet.cpp 顶部注释给出了一个非常直观的示例target 12, pos [10, 8, 0, 5, 3], speeds [2, 4, 1, 1, 3] - 3即位置 10 与 8 的车组成一队位置 0 的车单独一队位置 5 与 3 的车组成一队这个示例贯穿全文后续所有算法都会用它来验证。二、复杂度目标O(n log n) 时间、O(n) 空间hints/car-fleet.md 中给出的第一个提示即为本题的目标复杂度时间复杂度O(n log n)其中n是输入数组车辆数的大小空间复杂度O(n)。之所以时间下界是O(n log n)是因为无论用哪种思路都需要先对位置-速度对进行排序下文会解释为什么必须排序而空间O(n)则来自存放排序结果或栈结构本身。这也是面试中对该题的标准期望。三、Hint 1成对建模并按位置降序排序第一个提示建议先把所有车抽象成位置 速度的二维数据再把它们按位置降序排列。为什么先画图、再建模把每辆车画成数轴上的一个点横坐标是位置、旁边标注速度就能直观看到谁在前面、谁在后面。由于一辆车只能与它前方的车组成车队位置的先后关系是后续所有判定的基础因此必须把位置信息显式地打包进数组。成对建模以 Python 为例见 python/0853-car-fleet.py 第 3 行pair [(p, s) for p, s in zip(position, speed)] pair.sort(reverseTrue) # 按位置降序最靠近 target 的车排最前其他语言同样如此Java 用int[][2]二维数组java/0853-car-fleet.java 第 6-10 行Go 定义了carInfo{pos, spd}结构体go/0853-car-fleet.go 第 21-24 行Rust 用元组Vec(i32, i32)rust/0853-car-fleet.rs 第 3-7 行。四、Hint 2为什么必须按位置降序而不是升序这是本题最容易被忽略、也最关键的一步。提示原文点明了核心原因一辆车只能与前方的车组成车队因此按位置降序排序后每辆车的最终归属才清晰若按升序排序后一辆车在到达目标的过程中可能与另一辆车合并导致无法确定它的最终速度。具体来说降序closest to target first从离终点最近的车开始处理。处理到某辆车时它前方的车已经全部尘埃落定要么单独成队、要么已并入更前方的车队此时只需判断我能否追上前面那队即可不存在后续追及会改变我归属的歧义升序farthest from target first先处理离终点最远的车它后面还有更近的车可能追上它、改变整个车队的构成前面车的最终速度是悬而未决的导致判断无法进行。因此所有仓库实现都采用升序排序 从后往前遍历C、Java、C、Rust、TypeScript 等或直接降序排序 从前往后遍历Python、Go两种等价写法。例如 cpp/0853-car-fleet.cpp 先按位置升序sort(cars.begin(), cars.end())再for (int i n - 1; i 0; i--)从后往前扫而 Python 直接pair.sort(reverseTrue)后从前往后扫两者本质相同。五、Hint 3到达时间公式与成队判定条件时间公式每辆车到达target所需时间非常简单time (target - position) / speed仓库中几乎所有实现都直接使用该公式例如time (target - p) / stime : float64(target - p[0]) / float64(p[1])成队判定前方时间 后方时间提示给出的判定条件两辆车会组成车队当且仅当前方车辆到达 target 的时间 ≥ 后方车辆到达 target 的时间。直觉上很好理解后方车如果更快所需时间更短它就会在途中追上前面那辆车或那支车队从此以同一速度行驶到终点如果它所需时间恰好相等则两车会同时到达终点同样算作同一车队只有后方车更慢所需时间更长时它永远追不上前方才会形成新的一支车队。这个前方时间 ≥ 后方时间 → 合并的判定是 Hint 4 栈解法的核心比较逻辑。六、Hint 4用栈维护车队时间最后一个提示给出了数据结构层面的落地方案用栈维护各车队到达终点的时间。按位置降序遍历时计算每辆车到达 target 的时间并与栈顶前方车队的到达时间比较若当前时间 ≤ 栈顶时间则并入前方车队不入栈否则形成新车队入栈。最终栈的长度即车队总数。算法步骤栈解法将每辆车的(position, speed)配对按位置降序排序最靠近 target 的在前遍历每辆车计算time (target - position) / speed先压入栈若栈中元素 ≥ 2 且stack[-1] stack[-2]当前车不比前方车队慢说明它追上了前方车队弹出该时间合并不计数返回栈的长度即为车队总数。Python 参考实现python/0853-car-fleet.py 即该思路的完整实现class Solution: def carFleet(self, target: int, position: List[int], speed: List[int]) - int: pair [(p, s) for p, s in zip(position, speed)] pair.sort(reverseTrue) stack [] for p, s in pair: # Reverse Sorted Order stack.append((target - p) / s) if len(stack) 2 and stack[-1] stack[-2]: stack.pop() return len(stack)其他语言实现要点同一思路在仓库中覆盖了十余种语言这里列举部分实现细节Javajava/0853-car-fleet.java按位置升序排序后用StackDouble从尾部向前遍历若currentTime stack.peek()则跳过等价于弹出合并并带有if (position.length 1) return 1;的边界优化Gogo/0853-car-fleet.go用carInfo结构体数组按pos升序排序for i : len(pair) - 1; i 0; i--倒序遍历栈用[]float32切片模拟合并时stack stack[:len(stack)-1]Rustrust/0853-car-fleet.rsposition_speed_pair.sort_by(|a, b| a.0.partial_cmp(b.0).unwrap())升序后通过.iter().rev()倒序遍历注意比较时用stack.last() stack.get(stack.len() - 2)的 Option 比较写法TypeScripttypescript/0853-car-fleet.tscombined.sort((a, b) a[0] - b[0])升序后for (let i combined.length - 1; i -1; i--)倒序遍历。复杂度时间O(n log n)排序主导O(n)遍历每个元素最多入栈/出栈各一次空间O(n)存放 pair 数组与栈。七、进阶迭代法——只保留前一个车队时间栈解法中真正参与比较的永远只是当前与紧邻前方的那个车队时间。既然每合并一次就弹出栈顶那么栈中其实只活跃着最近的车队时间。于是可以把栈进一步压缩成一个变量prevTime这就是 articles/car-fleet.md 中介绍的第二种解法迭代法Iteration算法步骤配对并按位置降序排序初始化fleets 1第一辆、也是最靠近 target 的车自成一队prevTime为它的到达时间遍历剩余车辆计算currTime (target - position) / speed若currTime prevTime追不上前方车队fleets并把prevTime更新为currTime否则并入前方车队什么也不做返回fleets。仓库中的迭代法实现Kotlinkotlin/0853-car-fleet.ktclass Solution { fun carFleet(target: Int, position: IntArray, speed: IntArray): Int { val sortedPairs position .zip(speed) .sortedBy { (position, _) - position } var numberOfFleets 1 var timeRequiredForCarInFrontToReachInTarget (target - sortedPairs[sortedPairs.lastIndex].first) / sortedPairs[sortedPairs.lastIndex].second.toFloat() var timeRequiredForCurrentCarToReachTarget: Float for (i in (sortedPairs.lastIndex - 1) downTo 0) { timeRequiredForCurrentCarToReachTarget (target - sortedPairs[i].first) / sortedPairs[i].second.toFloat() if (timeRequiredForCurrentCarToReachTarget timeRequiredForCarInFrontToReachInTarget) { // 当前车到达终点所需时间比前方车队更长 → 追不上自成一队 numberOfFleets timeRequiredForCarInFrontToReachInTarget timeRequiredForCurrentCarToReachTarget } } return numberOfFleets } }Cc/0853-car-fleet.c通过qsort按位置升序排序后倒序遍历并用prevTime -1.0初始化同时处理了positionSize 0的空输入边界int carFleet(int target, int* position, int positionSize, int* speed, int speedSize) { if (positionSize 0) return 0; // ... 组装 Car 数组并 qsort 升序 ... int fleets 0; double prevTime -1.0; for (int i positionSize - 1; i 0; i--) { double time (double)(target - cars[i].position) / cars[i].speed; if (time prevTime) { fleets; prevTime time; } } return fleets; }Ccpp/0853-car-fleet.cpp的思路与 C 完全一致用double maxTime 0.0记录前方车队最大到达时间time maxTime时车队数 1。两种解法的关系栈解法通用性更强逻辑上显式地维护了所有车队的边界时间便于在纸上推演也更容易推广到需要回溯到任意一个历史车队的变体题迭代法观察到比较只依赖最近一次的车队时间空间从O(n)降到O(1)辅助空间排序的O(n)空间仍然存在代码更精简。从 articles/car-fleet.md 的两种解法复杂度标注看两者时间复杂度均为O(n log n)空间复杂度均为O(n)排序数组占主导因此面试中选哪种都可以重点是把降序处理 时间比较讲清楚。八、常见陷阱三个高频出错点articles/car-fleet.md 在 Common Pitfalls 一节中总结了三个最容易写错的点这里结合源码进一步说明1. 排序方向搞反升序排序离 target 最远的先处理会导致合并判定错误。必须保证先处理离 target 最近的车。# 错误升序 pair.sort() # 正确降序最靠近 target 的在前 pair.sort(reverseTrue)仓库实现中采用升序排序的语言Java、C、Go、Rust、Kotlin、TypeScript、C无一例外都配合从数组尾部向前遍历殊途同归。2. 用而不是比较到达时间当后方车与前方车队恰好同时到达target 时它们属于同一车队。若写成严格小于就会漏掉同时到达这一情况导致车队数偏多# 错误漏掉同时到达的情况 if stack[-1] stack[-2]: # 正确同时到达也算并入同一车队 if stack[-1] stack[-2]:3. 整数除法截断在 Java、C、C 等静态类型语言中两个整数相除会进行整数除法、直接截断小数部分而到达时间几乎总是小数截断后比较结果错误。必须显式转成浮点// 错误整数除法截断小数 int time (target - position) / speed; // 正确转 double 保证精度 double time (double)(target - position) / speed;这一点在仓库所有静态类型语言的实现中都有体现例如 C 的(double)(target - p.first) / p.second、Go 的float64(target - p[0]) / float64(p[1])、Rust 的(target - pos) as f64 / speed。九、仓库内配套资源一览本题在仓库中拥有完整的提示 → 详解文章 → 多语言源码配套体系可按需对照阅读逐步提示hints/car-fleet.md本文主题文档含 4 条提示完整解题文章articles/car-fleet.md含栈解法、迭代解法、多语言代码与常见陷阱多语言实现Pythonpython/0853-car-fleet.pyJavajava/0853-car-fleet.javaCcpp/0853-car-fleet.cppGogo/0853-car-fleet.goRustrust/0853-car-fleet.rsKotlinkotlin/0853-car-fleet.ktTypeScripttypescript/0853-car-fleet.tsCc/0853-car-fleet.c该仓库还覆盖了 C#、Swift、Ruby、Scala、Dart 等更多语言的版本见 README.md 的完整表格可作为多语言横向对比的学习素材。十、小结一套可复用的分析框架Car Fleet 的核心价值不在于题目本身而在于它浓缩了追及合并类问题的通用四步法建模把原始输入转成可排序的配对结构位置、速度排序确定处理顺序让前方已定、后方判定成为可能计算指标用(target - position) / speed把物理追及转化为纯数值比较合并计数用栈保留全部边界时间或单个变量只保留最近边界时间完成合并统计。掌握了这四步类似带约束的追赶/排队/合并计数类问题例如以时间或到达顺序为判据的单调栈应用都可以照此推演这也是该题在 NeetCode 题库中被归入栈专题的原因所在。【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考