LC406. 基于身高重建队列【贪心】

基于身高重建队列

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 值计算。

基于这个破局点,我们的策略呼之欲出:

  1. 先把高个子安排好位置。
  2. 再把矮个子一个个按他们自己的 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 时:

  1. 当前队列 res 里的所有人,身高都大于或等于当前这个人(因为我们是按身高从高到矮遍历的)。
  2. 当前这个人的要求是:"我前面要有 k 个比我高或一样高的人"。
  3. 既然队列里的人都满足"比我高或一样高",那么我只需要直接插到第 k 个位置 ,我前面就刚好有 k 个人了!
  4. 后顾之忧 :以后再插入的人会影响我吗?不会!因为以后插入的人都比我矮,就算他们插到我前面,我的 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]

开始一个个排队(插入):

  1. 叫到 [7,0]:前面要 0 个人。直接站到索引 0。 队列:[[7,0]]
  2. 叫到 [7,1]:前面要 1 个人。队列里 [7,0] 符合,站到索引 1。 队列:[[7,0], [7,1]]
  3. 叫到 [6,1]:前面要 1 个人。队列里目前两人都比他高,直接站到索引 1(插队到 [7,1] 前面)。 队列:[[7,0], [6,1], [7,1]] (注意看:[6,1] 插队没有影响 [7,1],因为 6 比 7 矮,[7,1] 依然只看到前面有一个大于等于 7 的 [7,0])
  4. 叫到 [5,0]:前面要 0 个人。直接站到索引 0。 队列:[[5,0], [7,0], [6,1], [7,1]]
  5. 叫到 [5,2]:前面要 2 个人。站到索引 2。 队列:[[5,0], [7,0], [5,2], [6,1], [7,1]]
  6. 叫到 [4,4]:前面要 4 个人。站到索引 4。 队列:[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

这就是 C++ 代码里那个简短的 insert 能够完美运作的原因。

你对这段代码中的 Lambda 表达式写法,或者 vector 的底层插入逻辑还有什么疑问吗?可以随时提出来。

相关推荐
忙什么果2 小时前
class_weight=‘balanced‘缓解类别不平衡问题
算法·机器学习·支持向量机
灰色小旋风2 小时前
力扣14 最长公共前缀(C++)
java·数据结构·算法
努力学习的小廉2 小时前
我爱学算法之——floodfill算法(下)
学习·算法
2401_851272992 小时前
编译器内建函数使用
开发语言·c++·算法
Rhystt2 小时前
代码随想录算法训练营第五十五天|图论理论基础、深搜理论基础、98. 所有可达路径、广搜理论基础
数据结构·c++·算法·深度优先·图论
Book思议-2 小时前
【数据结构实战】C 语言实现静态顺序队列:从原理到完整可运行代码
c语言·数据结构·算法·队列
hanlin032 小时前
刷题笔记:力扣第6题-Z字形变换
笔记·算法·leetcode
努力学习的小廉2 小时前
我爱学算法之——记忆化搜索
算法
m0_730115112 小时前
C++与Python混合编程实战
开发语言·c++·算法