406. 根据身高重建队列(贪心+简洁代码的线段树优化)
作者:互联网
题目描述:
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
示例 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 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,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]]
提示:
1 <= people.length <= 2000
0 <= hi <= 10^6
0 <= ki < people.length
题目数据确保队列可以被重建
通过次数82,348提交次数113,918
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/queue-reconstruction-by-height
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解析:
因为中文,题意就不说了。
看了这道题首先会想到的是贪心,然后又看到了 ki为前面大于等于自己身高的人数,因为做过求逆序数的题目,所以我会想到可以尝试着使用数状数组进行优化。下面我们需要怎么使用贪心呢?
其中hi表示当前人的身高,ki 表示 在当前人前面 大于等于hi的身高的人数, 怎么影响 ki,比自己身高小的不会影响。只有等于或大于自己的身高的才会影响。
贪心法(n^2):
① 在这里我们可以考虑插入排列身高法。按身高 从高到低排列,然后再按ki然后小到大排。
[[7,0],[7,1],[6,1],[5,0],[5,2],[4,4]] 6个位置,如以当前一个位置为基准,在这个位置之前都是有可能影响自身ki的人,在这个位置之后都不会影响到 自身ki。所以先把有可能影响到ki的排序,然后让自身 根据自身ki的值,进行插入即可,代码如下:
class Solution {
private:
static bool cmp(vector<int>& a,vector<int>& b){
if(a[0]==b[0]) return a[1]<b[1];
return a[0]>b[0];
}
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
int n=people.size();
sort(people.begin(),people.end(),cmp);
vector<vector<int> >queue;
for(int i=0;i<n;i++){
int pos=people[i][1];
queue.insert(queue.begin()+pos,people[i]);
}
return queue;
}
};
② 反过来想,我们按身高从低到高排序,按ki从大到小排序,
[[4,4],[5,2],[5,0],[6,1],[7,1],[7,0]] 以一个位置为基准,在这个位置之前都不会影响当前自身ki的值,在这一位置之后都是有可能影响到自身ki的值的。我们为后面应该影响到自身ki值得人,留空,就可以了。代码如下:
class Solution {
public:
int X;
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
bool a[1100]={0};
int n=people.size();
vector<vector<int>>ans(n);
sort(people.begin(),people.end(),[&](vector<int>&x,vector<int>&y)->bool{return x[0]<y[0]||(x[0]==y[0]&&x[1]>y[1]);});
int i,j,k;
for(i=0;i<n;++i){
for(j=k=0;;++j){
if(!a[j]){
if(++k>people[i][1])break;
}
}
a[j]=true;
ans[j]=people[i];
}
return ans;
}
};
③ 和上面②的排序方式不一样,思想一样。先按升高从低到高排序,若身高相等,按照ki值熊低到高排序。
class Solution {
public:
static bool cmp(vector<int> &a,vector<int> &b){
if(a[0]==b[0])
return a[1]<b[1];
else return a[0] < b[0];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(),people.end(),cmp);
vector<vector<int>> tmp(people.size(), vector<int>(2,-1));
for(int i = 0;i<people.size();i++){
vector<int > &tt = people[i];
int num = 0;
for(int j = 0;j<tmp.size();j++){
if(tmp[j][0]==-1)
num++;
else if(tmp[j][0]>=people[i][0])
num++;
if(num==people[i][1]+1){
tmp[j]=people[i];
break;
}
}
}
people = tmp;
return people;
}
};
贪心法+树状数组优化+二分优化(n*logn*logn)。
排序方式和 上面②一样,按身体从低到高排序,然后按照ki从高到低排序。这样排序序列的特点,上面已经说过了。留空法。
因为ki 表示 在当前人i 前面 大于等于hi的身高的人数,因为这句话让我想到了用树状数组优化。在这里我们设置树状数组bit[2048],大小根据people.length进行设置的。sum(i)表示在位置i之前(包含位置i)共有的空位数。代码如下:
class Solution {
private:
int X;
int bit[2048+1];
public:
Solution()
{
this->X=2048;
memset(this->bit,0,sizeof(this->bit));
}
static bool cmp(vector<int> a,vector<int> b){
if(a[0]==b[0])
return a[1] > b[1];
else return a[0] < b[0];
}
void add(int i,int val)
{
// bit[2048+1] +1就是因为这里是小于等于X
while(i<=X){
this->bit[i]+=val;
i+= i&-i;
}
}
int sum(int i)
{
int s=0;
while (i > 0)
{
s+=bit[i];
i-=i&-i;
}
return s;
}
int binary_search(int start,int end,int val)
{
int num=0;
while(start<=end)
{
int mid = (start + end)/2;
int s=sum(mid);
// 查找的都是从左到右数,第一个等于val+1的位置。
if(s==val+1){
num=mid;
end=mid-1;
}
else if(s<val+1)
start=mid+1;
else
end=mid-1;
}
return num;
}
vector<vector<int> > reconstructQueue(vector<vector<int> >& people) {
sort(people.begin(),people.end(),cmp);
vector<vector<int> > tmp(people.size(), vector<int>(2,-1));
int n=people.size();
for(int i = 1; i<=n; i++){
add(i,1);
}
for(int i = 0;i < n;i++)
{
int num = binary_search(1,n,people[i][1]);
if(num!=0){
tmp[num-1] = people[i];
add(num,-1);
}
}
people = tmp;
return people;
}
};
贪心+简洁代码的线段树优化 O(n*logn)
简洁构建线段树的代码:
// 构建树,这是一颗完全二叉树,如线段树构建数,X要为2的倍数。
for(int l=1, i=2,k=X; i<=2*X; i<<=1,k>>=1) // 每一层
for(j=i/2;j--;) // 每一层有多少节点
tree[l++]=k;
思想和上面一样,代码如下:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<bits/stdc++.h>
#include<vector>
using namespace std;
class Solution {
class Solution {
public:
static bool cmp( vector<int> a, vector<int> b ){
if ( a[0] == b[0] )
return a[1] > b[1];
else
return a[0] < b[0];
}
vector<vector<int> > reconstructQueue(vector<vector<int> >& people) {
int X=2048;
sort(people.begin(), people.end(), cmp);
vector<vector<int> >ans(people.size(),vector<int>(2,-1));
int tree[4*X+1];
int j,location_num, n = people.size();
// 构建树,这是一颗完全二叉树,如线段树构建数
for(int l=1, i=2,k=X; i<=2*X; i<<=1,k>>=1) // 每一层
for(j=i/2;j--;) // 每一层有多少节点
tree[l++]=k; // k为 以当前节点l为根的树,的空位数。
for(int i = 0; i < n;i++){
for(j=1,location_num=people[i][1];j<2*X;j<<=1){ // 注意这里是 j<2*X 不是小于等于,因为构建这棵线段树的所有节点数为2*X-1
if(location_num >= tree[j]){
location_num -= tree[j++];
}
tree[j]--;
}
j/=2; // 因为跳出的条件是j<4096,它的前一步为 j<<=1 ,也就是乘以2。只要跳出循环,就说明当前j节点,不在树内,它的上一个(父)节点在树内。j/=2
ans[j-X]=people[i];
}
people = ans;
return people;
}
};
int main()
{
int pp[6][2]={7,0,4,4,7,1,5,0,6,1,5,2};
vector<vector<int> >people(6,vector<int>(2,-1));
for(int i = 0;i<6;i++)
for(int j = 0;j<2;j++){
people[i][j]=pp[i][j];
}
Solution *A = new Solution();
A -> reconstructQueue(people);
for(int i=0;i<6;i++){
for(int j = 0;j<2;j++)
printf("%d ",people[i][j]);
printf("\n");
}
}
其实我有一个疑问,我在centos7上使用g++进行编译,若sort排序时加引用会报错:
static bool cmp( vector<int> &a, vector<int> &b ){
if ( a[0] == b[0] )
return a[1] > b[1];
else
return a[0] < b[0];
}
[root@rs3 algorithm]# g++ Greedy_line_trees.cpp -o Greedy_line_trees
In file included from /usr/include/c++/4.8.2/algorithm:62:0,
from Greedy_line_trees.cpp:3:
/usr/include/c++/4.8.2/bits/stl_algo.h: In instantiation of ‘_RandomAccessIterator std::__unguarded_partition(_RandomAccessIterator, _RandomAccessIterator, const _Tp&, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<std::vector<int>*, std::vector<std::vector<int> > >; _Tp = std::vector<int>; _Compare = bool (*)(std::vector<int>&, std::vector<int>&)]’:
/usr/include/c++/4.8.2/bits/stl_algo.h:2296:78: required from ‘_RandomAccessIterator std::__unguarded_partition_pivot(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<std::vector<int>*, std::vector<std::vector<int> > >; _Compare = bool (*)(std::vector<int>&, std::vector<int>&)]’
/usr/include/c++/4.8.2/bits/stl_algo.h:2337:62: required from ‘void std::__introsort_loop(_RandomAccessIterator, _RandomAccessIterator, _Size, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<std::vector<int>*, std::vector<std::vector<int> > >; _Size = long int; _Compare = bool (*)(std::vector<int>&, std::vector<int>&)]’
/usr/include/c++/4.8.2/bits/stl_algo.h:5499:44: required from ‘void std::sort(_RAIter, _RAIter, _Compare) [with _RAIter = __gnu_cxx::__normal_iterator<std::vector<int>*, std::vector<std::vector<int> > >; _Compare = bool (*)(std::vector<int>&, std::vector<int>&)]’
Greedy_line_trees.cpp:19:40: required from here
/usr/include/c++/4.8.2/bits/stl_algo.h:2263:35: 错误:将类型为‘std::vector<int>&’的引用初始化为类型为‘const std::vector<int>’的表达式无效
while (__comp(*__first, __pivot))
^
/usr/include/c++/4.8.2/bits/stl_algo.h:2266:34: 错误:将类型为‘std::vector<int>&’的引用初始化为类型为‘const std::vector<int>’的表达式无效
while (__comp(__pivot, *__last))
^
然后把引用去掉就能编译过,但是加引用的代码,在leetcode自带的编译器,可以编译过,而且耗时减半,我的疑问就是在g++编译器中,使用c++自带函数库中的sort排序,难道不能使用引用进行比较吗?感谢各位博主前来回答
下面是我的g++版本号:
[root@rs3 algorithm]# rpm -qa | grep gcc-c++
gcc-c++-4.8.5-44.el7.x86_64
标签:std,people,int,线段,406,vector,身高,ki,贪心 来源: https://blog.csdn.net/obsorb_knowledge/article/details/113940194