cpp
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
// 自定义排序规则:
// 1. 如果身高 a[0] 和 b[0] 相同,则按照 k 值(a[1] 和 b[1])升序排列
// 2. 否则,按照身高降序排列
sort(people.begin(), people.end(), [](const vector<int>& a, const vector<int>& b) {
if (a[0] == b[0]) return a[1] < b[1];
return a[0] > b[0];
});
vector<vector<int>> res;
// 遍历排序后的数组,根据 k 值将每个人插入到 res 的第 k 个位置
for (const vector<int>& p : people) {
res.insert(res.begin() + p[1], p);
}
return res;
}
};
- 时间复杂度 : O ( N 2 ) O(N^2) O(N2)。其中 N N N 是数组
people的长度。排序的时间复杂度是 O ( N log N ) O(N \log N) O(NlogN)。在结果数组中进行插入操作的平均/最坏时间复杂度是 O ( N ) O(N) O(N),遍历并插入 N N N 次,所以插入的总时间复杂度是 O ( N 2 ) O(N^2) O(N2)。综合来看整体时间复杂度为 O ( N 2 ) O(N^2) O(N2)。 - 空间复杂度 : O ( log N ) O(\log N) O(logN) 或 O ( N ) O(N) O(N)。取决于具体语言内部排序算法的栈空间开销,以及用来存储结果的数组/列表开销。
我们可以把这道题想象成在操场上排队。每个人的要求有两个维度:身高 h 和 前面比自己高或一样高的人数 k 。同时处理两个维度会顾此失彼,所以我们的核心思路是:先确定一个维度,再确定另一个维度。
以下是基于那份 C++ 代码的详细拆解:
1. 核心破局点:"矮个子对高个子是透明的"
题目要求 k 是"前面身高大于或等于 自己的人数"。 这意味着:如果你把高个子先排好,之后无论你怎么把矮个子插队到他们前面,都不会影响高个子的 k 值计算。
基于这个破局点,我们的策略呼之欲出:
- 先把高个子安排好位置。
- 再把矮个子一个个按他们自己的
k值插进队伍里。
2. 详细拆解第一步:自定义排序
C++
sort(people.begin(), people.end(), [](const vector<int>& a, const vector<int>& b) {
if (a[0] == b[0]) return a[1] < b[1]; // 规则 2:身高相同,k 小的排前面
return a[0] > b[0]; // 规则 1:按身高降序排(高的在前)
});
这段代码的目的是把所有的人排成一个特定的出场顺序。
- 规则 1:按身高降序排列 (
a[0] > b[0]) 让最高的人先出场。这样在后续插入时,每次处理的人都比队列里已经存在的人矮。 - 规则 2:身高相同时,按
k值升序排列 (a[1] < b[1]) 如果有两个人一样高(比如[7,0]和[7,1]),谁先出场?显然必须是[7,0]先出场排在最前面,然后[7,1]才能排在它后面。如果反过来,[7,1]先占了第 0 个位置,逻辑就乱了。
3. 详细拆解第二步:按索引插入
C++
vector<vector<int>> res;
for (const vector<int>& p : people) {
res.insert(res.begin() + p[1], p);
}
这是整个算法最"神奇"的一步。为什么直接把 p[1](也就是 k 值)当成插入的索引就可以了呢?
当我们拿出一个元素准备插入 res 时:
- 当前队列
res里的所有人,身高都大于或等于当前这个人(因为我们是按身高从高到矮遍历的)。 - 当前这个人的要求是:"我前面要有
k个比我高或一样高的人"。 - 既然队列里的人都满足"比我高或一样高",那么我只需要直接插到第
k个位置 ,我前面就刚好有k个人了! - 后顾之忧 :以后再插入的人会影响我吗?不会!因为以后插入的人都比我矮,就算他们插到我前面,我的
k值(只计算大于等于我身高的人)也不会发生任何改变。
4. 动画般的情景推演
假设输入是 [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
排序后,大家的出场顺序变成了: [7,0], [7,1], [6,1], [5,0], [5,2], [4,4]
开始一个个排队(插入):
- 叫到
[7,0]:前面要 0 个人。直接站到索引 0。 队列:[[7,0]] - 叫到
[7,1]:前面要 1 个人。队列里[7,0]符合,站到索引 1。 队列:[[7,0], [7,1]] - 叫到
[6,1]:前面要 1 个人。队列里目前两人都比他高,直接站到索引 1(插队到[7,1]前面)。 队列:[[7,0], [6,1], [7,1]](注意看:[6,1]插队没有影响[7,1],因为 6 比 7 矮,[7,1]依然只看到前面有一个大于等于 7 的[7,0]) - 叫到
[5,0]:前面要 0 个人。直接站到索引 0。 队列:[[5,0], [7,0], [6,1], [7,1]] - 叫到
[5,2]:前面要 2 个人。站到索引 2。 队列:[[5,0], [7,0], [5,2], [6,1], [7,1]] - 叫到
[4,4]:前面要 4 个人。站到索引 4。 队列:[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
这就是 C++ 代码里那个简短的 insert 能够完美运作的原因。
你对这段代码中的 Lambda 表达式写法,或者 vector 的底层插入逻辑还有什么疑问吗?可以随时提出来。