​LeetCode刷题实战406:根据身高重建队列

共 4168字,需浏览 9分钟

 ·

2021-10-12 23:51

算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !

今天和大家聊的问题叫做 根据身高重建队列,我们先来看题面:
https://leetcode-cn.com/problems/queue-reconstruction-by-height/

You are given an array of people, people, which are the attributes of some people in a queue (not necessarily in order). Each people[i] = [hi, ki] represents the ith person of height hi with exactly ki other people in front who have a height greater than or equal to hi.
Reconstruct and return the queue that is represented by the input array people. The returned queue should be formatted as an array queue, where queue[j] = [hj, kj] is the attributes of the jth person in the queue (queue[0] is the person at the front of the queue).

假设有打乱顺序的一群人站成一个队列,数组 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]]


解题

https://www.shangmayuan.com/a/08810c8da0fb4381a83a0a22.html


本题有两个维度,h和k,看到这种题目必定要想如何肯定一个维度,而后在按照另外一个维度从新排列。

其实若是你们认真作了贪心算法:分发糖果,就会发现和此题有点点的像。ide

在贪心算法:分发糖果我就强调过一次,遇到两个维度权衡的时候,必定要先肯定一个维度,再肯定另外一个维度。优化

「若是两个维度一块儿考虑必定会顾此失彼」。

对于本题相信你们困惑的点是先肯定k仍是先肯定h呢,也就是究竟先按h排序呢,还先按照k排序呢?

若是按照k来从小到大排序,排完以后,会发现k的排列并不符合条件,身高也不符合条件,两个维度哪个都没肯定下来。

那么按照身高h来排序呢,身高必定是从大到小排(身高相同的话则k小的站前面),让高个子在前面。

「此时咱们能够肯定一个维度了,就是身高,前面的节点必定都比本节点高!」

那么只须要按照k为下标从新插入队列就能够了,为何呢?

以图中{5,2} 为例:


按照身高排序以后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。

因此在按照身高从大到小排序后:

「局部最优:优先按身高高的people的k来插入。插入操做事后的people知足队列属性」

「全局最优:最后都作完插入操做,整个队列知足题目队列属性」

局部最优可推出全局最优,找不出反例,那就试试贪心。

一些同窗可能也会疑惑,你怎么知道局部最优就能够推出全局最优呢?有数学证实么?

在贪心系列开篇词关于贪心算法,你该了解这些!中,我已经讲过了这个问题了。

刷题或者面试的时候,手动模拟一下感受能够局部最优推出总体最优,并且想不到反例,那么就试一试贪心,至于严格的数学证实,就不在讨论范围内了。

若是没有读过关于贪心算法,你该了解这些!的同窗建议读一下,相信对贪心就有初步的了解了。

回归本题,整个插入过程以下:

排序完的people:
[[7,0], [7,1], [6,1], [5,0], [5,2],[4,4]]

插入的过程:插入[7,0]:[[7,0]]
插入[7,1]:[[7,0],[7,1]]
插入[6,1]:[[7,0],[6,1],[7,1]]
插入[5,0]:[[5,0],[7,0],[6,1],[7,1]]
插入[5,2]:[[5,0],[7,0],[5,2],[6,1],[7,1]]
插入[4,4]:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

此时就按照题目的要求完成了从新排列。
C++代码以下:


// 版本一
class Solution {
public:
    static bool cmp(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>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);
        vector<vector<int>> que;
        for (int i = 0; i < people.size(); i++) {
            int position = people[i][1];
            que.insert(que.begin() + position, people[i]);
        }
        return que;
    }
};


但使用vector是很是费时的,C++中vector(能够理解是一个动态数组,底层是普通数组实现的)若是插入元素大于预先普通数组大小,vector底部会有一个扩容的操做,即申请两倍于原先普通数组的大小,而后把数据拷贝到另外一个更大的数组上。

因此使用vector(动态数组)来insert,是费时的,插入再拷贝的话,单纯一个插入的操做就是O(n^2)了,甚至可能拷贝好几回,就不止O(n^2)了。

改为链表以后,C++代码以下:

// 版本二
class Solution {
public:
    // 身高从大到小排(身高相同k小的站前面)
    static bool cmp(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>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);
        list<vector<int>> que; // list底层是链表实现,插入效率比vector高的多
        for (int i = 0; i < people.size(); i++) {
            int position = people[i][1]; // 插入到下标为position的位置
            std::list<vector<int>>::iterator it = que.begin();
            while (position--) { // 寻找在插入位置
                it++;
            }
            que.insert(it, people[i]);
        }
        return vector<vector<int>>(que.begin(), que.end());
    }
};


好了,今天的文章就到这里,如果觉得有所收获,请顺手点个在看或者转发吧,你们的支持是我最大的动力 。

上期推文:

LeetCode1-400题汇总,希望对你有点帮助!

LeetCode刷题实战401:二进制手表

LeetCode刷题实战402:移掉 K 位数字

LeetCode刷题实战403:青蛙过河

LeetCode刷题实战404:左叶子之和

LeetCode刷题实战405:数字转换为十六进制数


浏览 32
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报