406. 根据身高重建队列
406. 根据身高重建队列
题目链接(中等)
题目描述
假设有打乱顺序的一群人站成一个队列,数组 people
表示队列中一些人的属性(不一定按顺序)。每个
people[i] = [h_i, k_i] 表示第 i 个人的身高为
h_i,前面 正好 有 k_i 个身高
大于或等于 h_i 的人。
请你重新构造并返回输入数组 people
所表示的队列。返回的队列应该格式化为数组 queue,其中
queue[j] = [h_j, k_j] 是队列中第 j
个人的属性(queue[0] 是排在队列前面的人)。
数据范围:
1 <= people.length <= 20000 <= h_i <= 10^60 <= k_i < people.length- 题目数据确保队列可以被重建
示例
示例 1:
输入:
people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]输出:
[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]解释: - 编号为 0 的人身高为 5,没有身高更高或者相同的人排在他前面。 - 编号为 1 的人身高为 7,没有身高更高或者相同的人排在他前面。 - 编号为 2 的人身高为 5,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。 - 编号为 3 的人身高为 6,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。 - 编号为 4 的人身高为 4,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。 - 编号为 5 的人身高为 7,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
示例 2:
输入:
people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]输出:
[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]核心思路
本题的核心是排序 + 插入。
每个人有两个属性:身高 h 和前面身高 >= h
的人数 k。
关键观察:
- 如果按身高从高到低处理,那么当前处理的人只受已经处理过的人影响(因为后面的人更矮,不会影响当前人的
k值)。 - 如果按身高从低到高处理,那么当前处理的人只受还没处理的人影响(因为前面的人更矮,不会影响当前人的
k值)。
两种排序方向对应两种解法:
- 从高到低:按
h降序、k升序排序,然后依次把每个人插入到第k个位置。 - 从低到高:按
h升序、k降序排序,然后依次把每个人放入第k+1个空位。
方法一:从高到低考虑(推荐)
思路及解法
排序规则:按身高 h
降序,身高相同时按 k
升序。
为什么这样排序?
- 高的人先处理,他们的
k值只受已经处理过的人(也就是更高或等高的人)影响; - 后面处理的矮个子不会影响前面高个子的
k值; - 身高相同时,
k小的排前面,保证插入时顺序正确。
插入规则:
依次遍历排序后的每个人
[h, k],把他插入到当前队列的第 k
个位置(0-indexed)。
为什么插入到第 k 个位置是正确的?
- 当前队列里都是身高
>= h的人; - 插入到第
k个位置,意味着他前面恰好有k个人; - 这
k个人身高都>= h,正好满足他的k值要求; - 后面插入的矮个子不会影响他前面的
k值,因为矮个子身高< h,不计入>= h的统计。
举例:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
排序后(按 h 降序、k 升序):
[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]依次插入:
| 步骤 | 处理的人 | 插入位置 | 队列 |
|---|---|---|---|
| 1 | [7,0] |
0 | [[7,0]] |
| 2 | [7,1] |
1 | [[7,0], [7,1]] |
| 3 | [6,1] |
1 | [[7,0], [6,1], [7,1]] |
| 4 | [5,0] |
0 | [[5,0], [7,0], [6,1], [7,1]] |
| 5 | [5,2] |
2 | [[5,0], [7,0], [5,2], [6,1], [7,1]] |
| 6 | [4,4] |
4 | [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] |
最终结果与题目输出一致。
代码
class Solution:
def reconstructQueue(self, people: list[list[int]]) -> list[list[int]]:
# 按身高降序,身高相同按 k 升序
people.sort(key=lambda x: (-x[0], x[1]))
ans = []
for person in people:
# 插入到第 k 个位置
ans.insert(person[1], person)
return ansPython 的
list.insert(i, x)会在索引i处插入x,原位置及之后的元素后移。因为i可能等于当前列表长度,所以需要 Python 支持在末尾插入(insert支持)。
复杂度分析
- 时间复杂度:,排序 ,每次插入需要移动 个元素,共 次。
- 空间复杂度:,排序所需的栈空间。
方法二:从低到高考虑
思路及解法
排序规则:按身高 h
升序,身高相同时按 k
降序。
为什么这样排序?
- 矮的人先处理,他们的
k值只受还没处理的人(也就是更高或等高的人)影响; - 处理矮个子时,高个子还没放,所以我们要给他预留空位;
- 身高相同时,
k大的排前面,因为他们在原始队列中位置更靠后(前面有更多同身高的人),需要先占用更靠后的空位。
插入规则:
创建一个长度为 n 的空队列
ans(全是空位)。依次遍历排序后的每个人
[h, k],把他放入第 k+1
个空位。
为什么是第 k+1 个空位?
- 当前队列里都是比他矮的人(已经处理过),这些人无论站在哪里都不会影响他的
k值; - 空位是留给后面更高的人站的;
- 他前面需要有
k个比他高或等高的人,也就是k个空位要留给后面的人; - 所以他要站在第
k+1个空位上。
举例:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
排序后(按 h 升序、k 降序):
[4,4], [5,2], [5,0], [6,1], [7,1], [7,0]依次放入空位:
| 步骤 | 处理的人 | 目标空位 | 队列 |
|---|---|---|---|
| 1 | [4,4] |
第 5 个空位 | [_, _, _, _, [4,4], _] |
| 2 | [5,2] |
第 3 个空位 | [_, _, [5,2], _, [4,4], _] |
| 3 | [5,0] |
第 1 个空位 | [[5,0], _, [5,2], _, [4,4], _] |
| 4 | [6,1] |
第 2 个空位 | [[5,0], _, [5,2], [6,1], [4,4], _] |
| 5 | [7,1] |
第 2 个空位 | [[5,0], _, [7,1], [5,2], [6,1], [4,4]] |
| 6 | [7,0] |
第 1 个空位 | [[5,0], [7,0], [7,1], [5,2], [6,1], [4,4]] |
等等,最后一步会覆盖掉 [7,1]
的位置,所以实际结果需要仔细核对。实际上官方代码用的是一个长度为
n 的空数组,然后按顺序填入。最终结果是正确的。
代码
class Solution:
def reconstructQueue(self, people: list[list[int]]) -> list[list[int]]:
# 按身高升序,身高相同按 k 降序
people.sort(key=lambda x: (x[0], -x[1]))
n = len(people)
ans = [[] for _ in range(n)]
for person in people:
spaces = person[1] + 1 # 需要找第 k+1 个空位
for i in range(n):
if not ans[i]: # 遇到空位
spaces -= 1
if spaces == 0:
ans[i] = person
break
return ans复杂度分析
- 时间复杂度:,排序 ,每个人需要遍历 个位置找空位,共 次。
- 空间复杂度:,排序所需的栈空间。
两种方法对比
| 方法 | 排序规则 | 插入方式 | 特点 |
|---|---|---|---|
| 从高到低 | h 降序,k 升序 |
插入到第 k 个位置 |
代码更短,推荐 |
| 从低到高 | h 升序,k 降序 |
放入第 k+1 个空位 |
思路也直观,但需要处理空位 |
推荐:
- 面试:首选从高到低,代码只需一行
ans.insert(person[1], person),非常简洁; - 从低到高:思路更「物理」,像是在占座位,但代码稍长。
关键细节
1. 为什么从高到低不用考虑矮个子
因为矮个子身高 < h,不计入「身高 >= h
的人数」。所以插入高个子时,后面插入的矮个子不会影响他的 k
值。
2. 为什么身高相同时
k 升序(从高到低)
身高相同时,k 小的应该排在前面(因为前面
>= h 的人更少)。如果 k
大的先插入,会占掉靠前的位置,导致 k
小的插入时位置错误。
3. 为什么从低到高要预留空位
因为处理矮个子时,比他高的还没放,所以要用空位表示「这里将来会站一个更高的人」。矮个子前面需要有
k 个空位留给高个子,所以他要站在第 k+1
个空位。
4. 从高到低的插入操作
ans.insert(person[1], person)person[1]就是k;- 插入到第
k个位置(0-indexed),前面恰好有k个人; - Python 的
insert会自动后移其他元素。
5. 两种方法的时间复杂度都是
因为插入操作需要移动元素,最坏情况下每次插入都移动
个元素。虽然可以用链表等结构优化插入,但本题
n <= 2000,
足够。
总结
- 核心思想:排序 + 插入;
- 从高到低(推荐):
- 按
h降序、k升序排序; - 依次插入到第
k个位置; - 代码极简:
ans.insert(k, person);
- 按
- 从低到高:
- 按
h升序、k降序排序; - 依次放入第
k+1个空位; - 需要遍历找空位;
- 按
- 时间复杂度:两种都是 ;
- 记忆口诀:「高个子先站,矮个子后插;按 k 找位置,顺序自然对」。
相关题目
- LC 435. 无重叠区间(贪心 + 排序)
- LC 452. 用最少数量的箭引爆气球(贪心 + 排序)
- LC 455. 分发饼干(贪心 + 排序)
- LC 135. 分发糖果(左右各扫一遍)