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

 

上一篇:【无标题】


下一篇:奇怪的电梯(信息学奥赛一本通 - T1360)