1122. Relative Sort Array - Explanation
Description
You are given two arrays arr1 and arr2, the elements of arr2 are distinct, and all elements in arr2 are also in arr1.
Sort the elements of arr1 such that the relative ordering of items in arr1 are the same as in arr2. Elements that do not appear in arr2 should be placed at the end of arr1 in ascending order.
Example 1:
Input: arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
Output: [2,2,2,1,4,3,3,9,6,7,19]Example 2:
Input: arr1 = [28,6,22,8,44,17], arr2 = [22,28,8,6]
Output: [22,28,8,6,17,44]Constraints:
1 <= arr1.length, arr2.length <= 10000 <= arr1[i], arr2[i] <= 1000- All the elements of
arr2are distinct. - Each
arr2[i]is inarr1.
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Hash Maps - Counting element frequencies and mapping values to their ordering priorities
- Sorting - Understanding built-in sort functions and custom comparators
- Counting Sort - Linear time sorting when the value range is bounded
1. Brute Force
Intuition
The straightforward approach is to process each element in arr2 in order and find all matching elements in arr1. For each value in arr2, we scan through arr1, collect all occurrences, and mark them as used. After processing all elements from arr2, any remaining elements in arr1 are sorted and appended to the result.
This approach directly mimics the problem requirements: first place elements in the order specified by arr2, then append the rest in sorted order.
Algorithm
- Create an empty result list.
- For each number in
arr2:- Scan through
arr1and find all occurrences of this number. - Add each occurrence to the result and mark the position in
arr1as used (e.g., set to-1).
- Scan through
- Sort the modified
arr1(marked positions will sort to the beginning). - Append all unmarked elements (those not equal to
-1) fromarr1to the result. - Return the result.
class Solution:
def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]:
res = []
for num2 in arr2:
for i, num1 in enumerate(arr1):
if num1 == num2:
res.append(num1)
arr1[i] = -1
arr1.sort()
for i in range(len(res), len(arr1)):
res.append(arr1[i])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- or depending on the sorting algorithm.
- space for the output list.
Where is the size of the array , and is the size of the array .
2. Hash Map
Intuition
Instead of repeatedly scanning arr1 for each element in arr2, we can first count the frequency of each element in arr1 using a hash map. This allows O(1) lookups when building the result.
We also use a set to quickly identify which elements from arr1 are not in arr2. These "extra" elements are collected separately, sorted, and appended to the end of the result.
Algorithm
- Create a set from
arr2forO(1)membership checks. - Count the frequency of each element in
arr1using a hash map. While counting, collect elements not inarr2into a separate list. - Sort the list of extra elements.
- Build the result: for each number in
arr2, append it to the result as many times as it appears inarr1. - Append the sorted extra elements to the result.
- Return the result.
class Solution:
def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]:
arr2_set = set(arr2)
arr1_count = defaultdict(int)
end = []
for num in arr1:
if num not in arr2_set:
end.append(num)
arr1_count[num] += 1
end.sort()
res = []
for num in arr2:
for _ in range(arr1_count[num]):
res.append(num)
return res + endTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the size of the array , and is the size of the array .
3. Hash Map (Optimal)
Intuition
This is a cleaner version of the hash map approach. Instead of tracking elements separately, we count all elements first, then process them in two phases: first by arr2 order, then by remaining keys in sorted order.
By removing keys from the hash map as we process arr2, whatever remains in the map represents elements not in arr2. We sort these remaining keys and append their occurrences to complete the result.
Algorithm
- Count the frequency of each element in
arr1using a hash map. - Build the result: for each number in
arr2, append it to the result according to its count, then remove it from the map. - Get the remaining keys from the map (elements not in
arr2) and sort them. - For each remaining key in sorted order, append it to the result according to its count.
- Return the result.
Note: This approach uses O(1) to O(n) depending on how elements are processed.
class Solution:
def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]:
count = {}
for num in arr1:
count[num] = count.get(num, 0) + 1
res = []
for num in arr2:
res += [num] * count.pop(num)
for num in sorted(count):
res += [num] * count[num]
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the size of the array , and is the size of the array .
4. Counting Sort
Intuition
When the range of values in arr1 is bounded and relatively small, counting sort becomes very efficient. We create an array where the index represents the value and the content represents the count.
The beauty of this approach is that the "remaining elements" are automatically sorted by simply iterating through the count array from index 0 to the maximum value. Elements that appear in arr2 are handled first, then we sweep through the count array for everything else.
Algorithm
- Find the maximum value in
arr1to determine the size of the count array. - Create a count array and populate it with frequencies of elements in
arr1. - Build the result: for each number in
arr2, append it according to its count and set its count to0. - Iterate from
0to the maximum value. For each index with a non-zero count, append that value to the result according to its count. - Return the result.
class Solution:
def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]:
max_val = max(arr1)
count = [0] * (max_val + 1)
for num in arr1:
count[num] += 1
res = []
for num in arr2:
res += [num] * count[num]
count[num] = 0
for num in range(len(count)):
res += [num] * count[num]
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output list.
Where is the size of the array , is the size of the array , and is the maximum value in the array .
5. Custom Sort
Intuition
Instead of manually placing elements, we can leverage the built-in sorting algorithm with a custom comparator. The key insight is to assign each element a "priority" based on its position in arr2.
Elements in arr2 get their index as priority (lower index = higher priority in the result). Elements not in arr2 get a large priority (like 1000 + value) so they sort after all arr2 elements, and among themselves they sort by their actual value.
Algorithm
- Create a hash map that maps each element in
arr2to its index. - Define a custom comparator: for each element, its sort key is its index in
arr2if present, otherwise1000 + element_value. - Sort
arr1using this custom comparator. - Return the sorted array.
This approach is elegant and leverages the natural sorting of arr2's indices to determine the final order.
Time & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output list.
Where is the size of the array , and is the size of the array .
Common Pitfalls
Forgetting to Sort Elements Not in arr2
After placing all elements that appear in arr2 according to their relative order, the remaining elements must be sorted in ascending order and appended to the result. A common mistake is appending these leftover elements in their original order or in the order they were encountered, rather than sorting them first.
Modifying arr1 While Iterating Over It
In brute force approaches that mark processed elements (e.g., setting them to -1), care must be taken to handle the iteration correctly. Modifying the array while iterating can lead to skipped elements or incorrect indexing. Using a separate data structure like a hash map to track counts avoids this issue entirely.
Sign in to join the discussion