0%

数组的相对排序

题目地址

难度:

题目描述:

给你两个数组,arr1 和 arr2,

arr2 中的元素各不相同
arr2 中的每个元素都出现在 arr1 中
对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。

示例:

1
2
输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
输出:[2,2,2,1,4,3,3,9,6,7,19]

提示:

  • arr1.length, arr2.length <= 1000
  • 0 <= arr1[i], arr2[i] <= 1000
  • arr2 中的元素 arr2[i] 各不相同
  • arr2 中的每个元素 arr2[i] 都出现在 arr1
🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️解题过程🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️
解题过程:

思路:

遍历数组arr2,对每一个元素在数组arr1中查找,若找到则添加到结果中并从arr1中删除。最后arr1中都是不在arr2中的元素,升序放在结果的末尾。

c++代码:(执行用时12ms,击败22.04%,内存消耗8M,击败9.72%)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
vector<int> result;
//遍历arr2中的元素
for(int i=0;i<arr2.size();++i){
//在arr1中查找该元素
vector<int>::iterator it;
while((it=find(arr1.begin(),arr1.end(),arr2[i]))!=arr1.end()){
result.push_back(arr2[i]);
//删除arr1中该元素,继续查找
arr1.erase(it);
}
}
//现在arr1中都是未在arr2中出现过的元素
//升序排序
sort(arr1.begin(),arr1.end());
//放在最后
result.insert(result.end(),arr1.begin(),arr1.end());
return result;
}
};
💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎官 方 题 解💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎
官方题解:

方法一:自定义排序

一种容易想到的方法是使用排序并自定义比较函数。

由于数组 $\textit{arr}_2$ 规定了比较顺序,因此我们可以使用哈希表对该顺序进行映射:即对于数组 $\textit{arr}_2$ 中的第 $i$ 个元素,我们将 $(\textit{arr}_2[i], i)$ 这一键值对放入哈希表 $\textit{rank}$中,就可以很方便地对数组 $\textit{arr}_1$中的元素进行比较。

比较函数的写法有很多种,例如我们可以使用最基础的比较方法,对于元素 $x$ 和$y$:

  • 如果 $x$ 和 $y$ 都出现在哈希表中,那么比较它们对应的值 $\textit{rank}[x]$和 $\textit{rank}[y]$;
  • 如果 $x$ 和 $y$ 都没有出现在哈希表中,那么比较它们本身;
  • 对于剩余的情况,出现在哈希表中的那个元素较小。

c++代码:(执行用时8ms,击败56.78%,内存消耗8M,击败9.16%)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
unordered_map<int, int> rank;
for (int i = 0; i < arr2.size(); ++i) {
rank[arr2[i]] = i;
}
sort(arr1.begin(), arr1.end(), [&](int x, int y) {
if (rank.count(x)) {
return rank.count(y) ? rank[x] < rank[y] : true;
}
else {
return rank.count(y) ? false : x < y;
}
});
return arr1;
}
};

很多语言支持对「元组」进行排序,即依次比较元组中每一个对应位置的元素,直到比较出大小关系为止。因此,对于元素 $x$,如果它出现在哈希表中,我们使用二元组 $(0, \textit{rank}[x])$;如果它没有出现在哈希表中,我们使用二元组 $(1, x)$,就可以得到正确的排序结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
unordered_map<int, int> rank;
for (int i = 0; i < arr2.size(); ++i) {
rank[arr2[i]] = i;
}
auto mycmp = [&](int x) -> pair<int, int> {
return rank.count(x) ? pair{0, rank[x]} : pair{1, x};
};
sort(arr1.begin(), arr1.end(), [&](int x, int y) {
return mycmp(x) < mycmp(y);
});
return arr1;
}
};

此外,由于题目中给定的元素都是正数,因此我们可以用 $\textit{rank}[x]-n$ 和 $x$分别代替 $(0, \textit{rank}[x])$ 和 $(1, x)$,其中 $n$ 是数组 $\textit{arr}_2$的长度(同时也是哈希表 $\textit{rank}$ 的大小)。这样做的正确性在于,$\textit{rank}[x]-n$一定是负数,而 $x$ 一定是正数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
unordered_map<int, int> rank;
int n = arr2.size();
for (int i = 0; i < n; ++i) {
rank[arr2[i]] = i - n;
}
sort(arr1.begin(), arr1.end(), [&](int x, int y) {
return (rank.count(x) ? rank[x] : x) < (rank.count(y) ? rank[y] : y);
});
return arr1;
}
};

复杂度分析

  • 时间复杂度:$O(m \log m + n)$,其中 $m$ 和 $n$ 分别是数组 $\textit{arr}_1$ 和 $\textit{arr}_2$ 的长度。构造哈希表 $\textit{rank}$ 的时间复杂度为 $O(n)$,排序的时间复杂度为 $O(m \log m)$。

  • 空间复杂度:$O(\log m + n)$,哈希表 $\textit{rank}$需要的空间为 $O(n)$,排序需要的栈空间为 $O(\log m)$

方法二:计数排序

思路与算法

注意到本题中元素的范围为 $[0, 1000]$,这个范围不是很大,我们也可以考虑不基于比较的排序,例如「计数排序」。

具体地,我们使用一个长度为 $1001$(下标从 $0$ 到 $1000$)的数组 $\textit{frequency}$,记录每一个元素在数组 中出现的次数。随后我们遍历数组 $\textit{arr}_2$ ,当遍历到元素 $x$ 时,我们将 $\textit{frequency}[x]$个 $x$ 加入答案中,并将 $\textit{frequency}[x]$清零。当遍历结束后,所有在 $\textit{arr}_2$中出现过的元素就已经有序了。

此时还剩下没有在 $\textit{arr}_2$中出现过的元素,因此我们还需要对整个数组 $\textit{frequency}$进行一次遍历。当遍历到元素 $x$时,如果 $\textit{frequency}[x]$不为 00,我们就将 $\textit{frequency}[x]$个 $x$加入答案中。

细节

我们可以对空间复杂度进行一个小优化。实际上,我们不需要使用长度为 $1001$ 的数组,而是可以找出数组 $\textit{arr}_1$ 中的最大值 $\textit{upper}$,使用长度为 $\textit{upper}+1$ 的数组即可。

c++代码:(执行用时0ms,击败100.00%,内存消耗7.7M,击败51.63%)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
int upper = *max_element(arr1.begin(), arr1.end());
vector<int> frequency(upper + 1);
for (int x: arr1) {
++frequency[x];
}
vector<int> ans;
for (int x: arr2) {
for (int i = 0; i < frequency[x]; ++i) {
ans.push_back(x);
}
frequency[x] = 0;
}
for (int x = 0; x <= upper; ++x) {
for (int i = 0; i < frequency[x]; ++i) {
ans.push_back(x);
}
}
return ans;
}
};

复杂度分析

  • 时间复杂度:$O(m + n + \textit{upper})$,其中 $m$ 和 $n$ 分别是数组 $\textit{arr}_1$ 和 $\textit{arr}_2$ 的长度,$\textit{upper}$是数组 $\textit{arr}_1$
    中的最大值,在本题中 $\textit{upper}$不会超过 $1000$。遍历数组 $\textit{arr}_2$ 的时间复杂度为 $O(n)$,遍历数组 $\textit{frequency}$的时间复杂度为 $O(\textit{upper})$,而在遍历的过程中,我们一共需要 $O(m)$ 的时间得到答案数组。
  • 空间复杂度:$O(\textit{upper})$,即为数组 $\textit{frequency}$ 需要使用的空间。注意到与方法一不同的是,方法二的代码使用了额外的空间(而不是直接覆盖数组 $\textit{arr}_1$ )存放答案,但我们一般不将存储返回答案的数组计入空间复杂度,并且在我们得到数组 $\textit{frequency}$之后,实际上也是可以将返回答案覆盖在数组 $\textit{arr}_1$ 上的。如果在面试中遇到了本题,这些细节都可以和面试官进行确认。
⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳总 结⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳
总结:

自定义排序容易想到吗?还是暴力解法比较香😎。可以学习自定义排序的思想,计数排序也遇到过好多次了,也是常用的解题方法。

------------- THE END! THANKS! -------------