难度:⭐
题目描述:
给你两个数组,arr1 和 arr2,
arr2 中的元素各不相同
arr2 中的每个元素都出现在 arr1 中
对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。
示例:
1 | 输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6] |
提示:
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 | class Solution { |
官方题解:
方法一:自定义排序
一种容易想到的方法是使用排序并自定义比较函数。
由于数组 $\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 | class Solution { |
很多语言支持对「元组」进行排序,即依次比较元组中每一个对应位置的元素,直到比较出大小关系为止。因此,对于元素 $x$,如果它出现在哈希表中,我们使用二元组 $(0, \textit{rank}[x])$;如果它没有出现在哈希表中,我们使用二元组 $(1, x)$,就可以得到正确的排序结果。
1 | class Solution { |
此外,由于题目中给定的元素都是正数,因此我们可以用 $\textit{rank}[x]-n$ 和 $x$分别代替 $(0, \textit{rank}[x])$ 和 $(1, x)$,其中 $n$ 是数组 $\textit{arr}_2$的长度(同时也是哈希表 $\textit{rank}$ 的大小)。这样做的正确性在于,$\textit{rank}[x]-n$一定是负数,而 $x$ 一定是正数。
1 | class Solution { |
复杂度分析
时间复杂度:$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 | class Solution { |
复杂度分析
- 时间复杂度:$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$ 上的。如果在面试中遇到了本题,这些细节都可以和面试官进行确认。
总结:
自定义排序容易想到吗?还是暴力解法比较香😎。可以学习自定义排序的思想,计数排序也遇到过好多次了,也是常用的解题方法。