Leetcode(406)——根据身高重建队列
题目
假设有打乱顺序的一群人站成一个队列,数组 p e o p l e people people 表示队列中一些人的属性(不一定按顺序)。每个 p e o p l e [ i ] = [ h i , k i ] people[i] = [hi, ki] people[i]=[hi,ki] 表示第 i i i 个人的身高为 h i hi hi,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 p e o p l e people people 所表示的队列。返回的队列应该格式化为数组 q u e u e queue queue,其中 q u e u e [ j ] = [ h j , k j ] queue[j] = [hj, kj] queue[j]=[hj,kj] 是队列中第 j j j 个人的属性( q u e u e [ 0 ] queue[0] 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 1 1 <= people.length <= 2000 2000 2000
- 0 0 0 <= hi <= 1 0 6 10^6 106
- 0 0 0 <= ki < people.length
- 题目数据确保队列可以被重建
题解
方法一:贪心(从高到低排序+插入)
思路
同样地,我们也可以将每个人按照身高从大到小进行排序,处理身高相同的人使用的方法类似,即:按照 h i h_i hi 为第一关键字降序, k i k_i ki 为第二关键字升序进行排序。如果我们按照排完序后的顺序,依次将每个人放入队列中,那么当我们放入第 i i i 个人时:
- 第 0 , ⋯ , i − 1 0, \cdots, i-1 0,⋯,i−1 个高的人已经在队列中被安排了位置,他们只要站在第 i i i 个人的前面,就会对第 i i i 个人产生影响,因为他们都比第 i i i 个人高;
- 而第 i + 1 , ⋯ , n − 1 i+1, \cdots, n-1 i+1,⋯,n−1 个高的人还没有被放入队列中,并且他们无论站在哪里,对第 i i i 个人都没有任何影响,因为他们都比第 i i i 个人矮。
在这种情况下,我们无从得知应该给后面的人安排多少个「空」位置,因此就不能沿用方法二。但我们可以发现,后面的人既然不会对第 i i i 个人造成影响,我们可以采用「插空」的方法,依次给每一个人在当前的队列中选择一个插入的位置。也就是说,当我们放入第 i i i 个人时,只需要将其插入队列中,使得他的前面恰好有 k i k_i ki 个人即可。
代码实现
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
// 现在的 people 不一定按顺序,我们需要重新排序,实现下面要求:
// 每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人
// 从后面开始排序?可能会好点。选择从高到低会好点?试试
// 排序,people[n][0] 大的在前面,people[n][0] 相同时 people[n][1]小的在前面
sort(people.begin(), people.end(),
[](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>> ans;
int size = people.size();
for(int n = 0; n < size; n++){
// 每插入一个新值时,因为根据people[0]从大到小插入,此时在 ans 中的数都大于或等于它
// 所以根据 people[1] 选择排在第几,比如为0时,前面没有大于等于它的值了,又因为此时在 ans 中的数都大于或等于它
// 所以它插入后在第一位
if(ans.empty()) ans.push_back(people[n]);
else ans.insert(ans.begin()+people[n][1], people[n]);
}
return ans;
}
};
复杂度分析
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),其中
n
n
n 是数组
people
\textit{people}
people 的长度。我们需要
O
(
n
log
n
)
O(n \log n)
O(nlogn) 的时间进行排序,随后需要
O
(
n
2
)
O(n^2)
O(n2) 的时间遍历每一个人并将他们放入队列中。由于前者在渐近意义下小于后者,因此总时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
空间复杂度:
O
(
log
n
)
O(\log n)
O(logn),即为排序需要使用的栈空间。
方法二:贪心(从低到高排序+创建)
思路
当每个人的身高都不相同时,如果我们将他们按照身高从小到大进行排序,那么就可以很方便地还原出原始的队列了。
为了叙述方便,我们设人数为 n n n,在进行排序后,它们的身高依次为 h 0 , h 1 , ⋯ , h n − 1 h_0, h_1, \cdots, h_{n-1} h0,h1,⋯,hn−1,且排在第 i i i 个人前面身高大于 h i h_i hi 的人数为 k i k_i ki。如果我们按照排完序后的顺序,依次将每个人放入队列中,那么当我们放入第 i i i 个人时:
- 第 0 , ⋯ , i − 1 0, \cdots, i-1 0,⋯,i−1 个矮的人已经在队列中被安排了位置,并且他们无论站在哪里,对第 i i i 个人都没有任何影响,因为他们都比第 i i i 个人矮;
- 而第 i + 1 , ⋯ , n − 1 i+1, \cdots, n-1 i+1,⋯,n−1 个矮的人还没有被放入队列中,但他们只要站在第 i i i 个人的前面,就会对第 i i i 个人产生影响,因为他们都比第 i i i 个人高。
如果我们在初始时建立一个包含 n n n 个位置的空队列,而我们每次将一个人放入队列中时,会将一个「空」位置变成「满」位置,那么当我们放入第 i i i 个人时,我们需要给他安排一个「空」位置,并且这个「空」位置前面恰好还有 k i k_i ki 个「空」位置,用来安排给后面身高更高的人。也就是说,第 i i i 个人的位置,就是队列中从左往右数第 k i + 1 k_i+1 ki+1 个「空」位置。
那么如果有身高相同的人,上述 k i k_i ki 定义中的大于就与题目描述中要求的大于等于不等价了,此时应该怎么修改上面的方法呢?我们可以这样想,如果第 i i i 个人和第 j j j 个人的身高相同,即 h i = h j h_i = h_j hi=hj,那么我们可以把在队列中处于较后位置的那个人的身高减小一点点。换句话说,对于某一个身高值 h h h,我们将队列中第一个身高为 h h h 的人保持不变,第二个身高为 h h h 的人的身高减少 δ \delta δ,第三个身高为 h h h 的人的身高减少 2 δ 2\delta 2δ,以此类推,其中 δ \delta δ 是一个很小的常数,它使得任何身高为 h h h 的人不会与其它(身高不为 h h h 的)人造成影响。
如何找到第一个、第二个、第三个身高为 h h h 的人呢?我们可以借助 k k k 值,可以发现:当 h i = h j h_i=h_j hi=hj 时,如果 k i > k j k_i > k_j ki>kj,那么说明 i i i 一定相对于 j j j 在队列中处于较后的位置(因为在第 j j j 个人之前比他高的所有人,一定都比第 i i i 个人要高),按照修改之后的结果, h i h_i hi 略小于 h j h_j hj,第 i i i 个人在排序后应该先于第 j j j 个人被放入队列。因此,我们不必真的去对身高进行修改,而只需要按照 h i h_i hi 为第一关键字升序, k i k_i ki 为第二关键字降序进行排序即可。此时,具有相同身高的人会按照它们在队列中的位置逆序进行排列,也就间接实现了上面将身高减少 δ \delta δ 这一操作的效果。
这样一来,我们只需要使用一开始提到的方法,将第 i i i 个人放入队列中的第 k i + 1 k_i+1 ki+1 个空位置,即可得到原始的队列。
代码实现
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
// 排序,people[n][0] 小的在前面,people[n][0] 相同时 people[n][1]大的在前面
sort(people.begin(), people.end(), [](vector<int>& A, vector<int>& B){
if(A[0] == B[0]) return A[1] > B[1];
else return A[0] < B[0];
});
int size = people.size();
int space;
vector<vector<int>> ans(size);
for(int n = 0; n < size; n++){
space = people[n][1];
for(int i = 0; i < size; i++){
if(ans[i].empty()) space--;
if(space < 0){
ans[i].insert(ans[i].end(), {people[n][0], people[n][1]});
break;
}
}
}
return ans;
}
};
复杂度分析
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),其中
n
n
n 是数组
people
\textit{people}
people 的长度。我们需要
O
(
n
log
n
)
O(n \log n)
O(nlogn) 的时间进行排序,随后需要
O
(
n
2
)
O(n^2)
O(n2) 的时间遍历每一个人并将他们放入队列中。由于前者在渐近意义下小于后者,因此总时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
空间复杂度:
O
(
log
n
)
O(\log n)
O(logn),即为排序需要使用的栈空间。