760. Find Anagram Mappings 🔒
Problem Description
You are given two integer arrays nums1
and nums2
, where nums2
is an anagram of nums1
. This means nums2
contains exactly the same elements as nums1
, but potentially in a different order. Both arrays may contain duplicate values.
Your task is to create an index mapping array called mapping
that shows where each element from nums1
appears in nums2
. Specifically, mapping[i] = j
means that the element at position i
in nums1
can be found at position j
in nums2
.
For example:
- If
nums1 = [12, 28, 46, 32, 50]
andnums2 = [50, 12, 32, 46, 28]
- The output could be
[1, 4, 3, 2, 0]
because:nums1[0] = 12
appears atnums2[1]
, somapping[0] = 1
nums1[1] = 28
appears atnums2[4]
, somapping[1] = 4
nums1[2] = 46
appears atnums2[3]
, somapping[2] = 3
- And so on...
When there are duplicate elements in the arrays, each element in nums1
should be mapped to one of its occurrences in nums2
. Any valid mapping is acceptable - you don't need to find a specific one.
The solution uses a hash map to store all indices where each value appears in nums2
, then for each element in nums1
, it retrieves and uses one of those indices from the hash map.
Intuition
Since we need to find where each element from nums1
appears in nums2
, the most straightforward approach is to first record all positions of each element in nums2
. This way, when we iterate through nums1
, we can quickly look up where each element exists in nums2
.
The key insight is that we're essentially solving a lookup problem - for each element in nums1
, we need to find its position in nums2
. Rather than searching through nums2
every time we need to find an element (which would be inefficient with O(n²) time complexity), we can preprocess nums2
once to build a mapping of values to their indices.
Why use a set for storing indices? Since the arrays are anagrams, every element in nums1
is guaranteed to exist in nums2
. When duplicates exist, a single value might appear at multiple indices in nums2
. Using a set allows us to store all possible indices for each value, and then we can simply pop()
one index when we need it. The pop()
operation ensures that each index in nums2
is used only once, maintaining a valid one-to-one mapping.
The beauty of this approach is that it handles duplicates naturally - if a value appears multiple times in both arrays, we store all its positions from nums2
and consume them one by one as we encounter the same value in nums1
. Since both arrays are anagrams, we're guaranteed to have exactly the right number of indices for each value.
Solution Approach
The implementation uses a hash map with sets to efficiently solve this problem:
-
Build the index map from
nums2
:- Create a
defaultdict(set)
calledmapper
to store value-to-indices mappings - Iterate through
nums2
with enumeration to get both indexi
and valuenum
- For each element, add its index to the set of indices for that value:
mapper[num].add(i)
- This handles duplicates by storing all indices where each value appears
- Create a
-
Create the mapping array:
- Use a list comprehension to iterate through each element in
nums1
- For each element
num
innums1
, accessmapper[num]
to get the set of available indices - Use
pop()
to remove and return one index from the set - This ensures each index from
nums2
is used exactly once
- Use a list comprehension to iterate through each element in
Why this works:
- Since
nums2
is an anagram ofnums1
, every element innums1
is guaranteed to exist innums2
with the same frequency - The
pop()
operation removes an arbitrary element from the set, which satisfies the requirement that "any valid mapping" is acceptable - Time complexity: O(n) where n is the length of the arrays - we iterate through
nums2
once to build the map, then throughnums1
once to build the result - Space complexity: O(n) for storing the hash map and the result array
The algorithm elegantly handles all cases including duplicates, as the set automatically manages multiple occurrences of the same value, and we consume indices one at a time as needed.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with duplicates to illustrate the solution approach.
Given:
nums1 = [4, 2, 4, 3]
nums2 = [3, 4, 2, 4]
Step 1: Build the index map from nums2
We iterate through nums2
and record where each value appears:
- Index 0: value = 3 →
mapper[3] = {0}
- Index 1: value = 4 →
mapper[4] = {1}
- Index 2: value = 2 →
mapper[2] = {2}
- Index 3: value = 4 →
mapper[4] = {1, 3}
(4 appears again, so we add index 3)
After processing nums2
, our mapper looks like:
mapper = { 3: {0}, 4: {1, 3}, 2: {2} }
Step 2: Create the mapping array
Now we process each element in nums1
:
-
nums1[0] = 4
:- Look up
mapper[4] = {1, 3}
- Pop one index (let's say we get 1)
mapping[0] = 1
mapper[4]
now contains{3}
- Look up
-
nums1[1] = 2
:- Look up
mapper[2] = {2}
- Pop the index 2
mapping[1] = 2
mapper[2]
now empty
- Look up
-
nums1[2] = 4
:- Look up
mapper[4] = {3}
- Pop the index 3
mapping[2] = 3
mapper[4]
now empty
- Look up
-
nums1[3] = 3
:- Look up
mapper[3] = {0}
- Pop the index 0
mapping[3] = 0
mapper[3]
now empty
- Look up
Final Result: mapping = [1, 2, 3, 0]
We can verify this is correct:
nums1[0] = 4
maps tonums2[1] = 4
✓nums1[1] = 2
maps tonums2[2] = 2
✓nums1[2] = 4
maps tonums2[3] = 4
✓nums1[3] = 3
maps tonums2[0] = 3
✓
Notice how the algorithm naturally handled the duplicate value (4) by storing both of its indices and using them one at a time.
Solution Implementation
1from collections import defaultdict
2from typing import List
3
4class Solution:
5 def anagramMappings(self, nums1: List[int], nums2: List[int]) -> List[int]:
6 # Create a dictionary to store indices of each number in nums2
7 # Using set to handle duplicate values and allow efficient removal
8 index_map = defaultdict(set)
9
10 # Build the mapping: number -> set of indices where it appears in nums2
11 for index, number in enumerate(nums2):
12 index_map[number].add(index)
13
14 # For each number in nums1, retrieve and remove one matching index from nums2
15 # pop() removes and returns an arbitrary element from the set
16 result = [index_map[number].pop() for number in nums1]
17
18 return result
19
1class Solution {
2 public int[] anagramMappings(int[] nums1, int[] nums2) {
3 // Create a map to store each value in nums2 with its corresponding indices
4 // Key: value from nums2, Value: set of indices where this value appears
5 Map<Integer, Set<Integer>> valueToIndicesMap = new HashMap<>();
6
7 // Build the map by iterating through nums2
8 // Store all indices for each value (handles duplicates)
9 for (int i = 0; i < nums2.length; i++) {
10 valueToIndicesMap.computeIfAbsent(nums2[i], k -> new HashSet<>()).add(i);
11 }
12
13 // Initialize result array to store the mapping
14 int[] result = new int[nums1.length];
15
16 // For each element in nums1, find its corresponding index in nums2
17 for (int i = 0; i < nums1.length; i++) {
18 // Get any available index for the current value from the set
19 int indexInNums2 = valueToIndicesMap.get(nums1[i]).iterator().next();
20
21 // Store the mapping in the result array
22 result[i] = indexInNums2;
23
24 // Remove the used index to avoid reusing it for duplicate values
25 valueToIndicesMap.get(nums1[i]).remove(indexInNums2);
26 }
27
28 return result;
29 }
30}
31
1class Solution {
2public:
3 vector<int> anagramMappings(vector<int>& nums1, vector<int>& nums2) {
4 // Create a map to store each value in nums2 with its corresponding indices
5 // Key: value from nums2, Value: set of indices where this value appears
6 unordered_map<int, unordered_set<int>> value_to_indices_map;
7
8 // Build the map by iterating through nums2
9 // Store all indices for each value (handles duplicates)
10 for (int i = 0; i < nums2.size(); i++) {
11 value_to_indices_map[nums2[i]].insert(i);
12 }
13
14 // Initialize result array to store the mapping
15 vector<int> result(nums1.size());
16
17 // For each element in nums1, find its corresponding index in nums2
18 for (int i = 0; i < nums1.size(); i++) {
19 // Get any available index for the current value from the set
20 int index_in_nums2 = *value_to_indices_map[nums1[i]].begin();
21
22 // Store the mapping in the result array
23 result[i] = index_in_nums2;
24
25 // Remove the used index to avoid reusing it for duplicate values
26 value_to_indices_map[nums1[i]].erase(index_in_nums2);
27 }
28
29 return result;
30 }
31};
32
1function anagramMappings(nums1: number[], nums2: number[]): number[] {
2 // Create a map to store each value in nums2 with its corresponding indices
3 // Key: value from nums2, Value: set of indices where this value appears
4 const valueToIndicesMap: Map<number, Set<number>> = new Map();
5
6 // Build the map by iterating through nums2
7 // Store all indices for each value (handles duplicates)
8 for (let i = 0; i < nums2.length; i++) {
9 if (!valueToIndicesMap.has(nums2[i])) {
10 valueToIndicesMap.set(nums2[i], new Set());
11 }
12 valueToIndicesMap.get(nums2[i])!.add(i);
13 }
14
15 // Initialize result array to store the mapping
16 const result: number[] = new Array(nums1.length);
17
18 // For each element in nums1, find its corresponding index in nums2
19 for (let i = 0; i < nums1.length; i++) {
20 // Get the set of available indices for the current value
21 const indicesSet = valueToIndicesMap.get(nums1[i])!;
22
23 // Get any available index from the set using iterator
24 const indexInNums2 = indicesSet.values().next().value;
25
26 // Store the mapping in the result array
27 result[i] = indexInNums2;
28
29 // Remove the used index to avoid reusing it for duplicate values
30 indicesSet.delete(indexInNums2);
31 }
32
33 return result;
34}
35
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input arrays.
- Building the mapper dictionary takes
O(n)
time as we iterate throughnums2
once - Creating the result list with list comprehension takes
O(n)
time as we iterate throughnums1
once - The
pop()
operation on a set isO(1)
on average - Total:
O(n) + O(n) = O(n)
Space Complexity: O(n)
where n
is the length of the input arrays.
- The
mapper
dictionary stores at mostn
key-value pairs (in the worst case when all elements innums2
are unique) - Each value in the dictionary is a set that collectively stores
n
indices across all sets - The output list requires
O(n)
space - Total auxiliary space (excluding output):
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Using a List Instead of a Set for Index Storage
The Problem: A common mistake is using a list to store indices instead of a set:
# Incorrect approach
index_map = defaultdict(list)
for index, number in enumerate(nums2):
index_map[number].append(index)
# Then trying to remove used indices
result = []
for number in nums1:
result.append(index_map[number].pop(0)) # O(n) operation!
Why it's problematic:
pop(0)
on a list is an O(n) operation because all remaining elements need to shift- This degrades the overall time complexity from O(n) to O(n²) in the worst case
- For large arrays with many duplicates, this becomes significantly slower
The Solution: Use a set (as in the original code) or pop from the end of the list:
# Option 1: Use set with pop() - O(1) average
index_map = defaultdict(set)
result = [index_map[number].pop() for number in nums1]
# Option 2: Use list but pop from the end - O(1)
index_map = defaultdict(list)
result = [index_map[number].pop() for number in nums1] # pop() without argument
Pitfall 2: Not Handling the Mutation of the Index Map
The Problem: If you need to use the solution multiple times or preserve the original mapping for later use:
def anagramMappings(self, nums1, nums2):
index_map = defaultdict(set)
for index, number in enumerate(nums2):
index_map[number].add(index)
# First call works fine
result1 = [index_map[number].pop() for number in nums1]
# Second call fails - sets are now empty!
result2 = [index_map[number].pop() for number in nums1] # KeyError!
The Solution: If you need to preserve the mapping, create a copy or use a non-destructive approach:
# Option 1: Create a copy for consumption
import copy
index_map_copy = copy.deepcopy(index_map)
result = [index_map_copy[number].pop() for number in nums1]
# Option 2: Use iterator-based approach (non-destructive)
from collections import defaultdict, deque
index_map = defaultdict(deque)
for index, number in enumerate(nums2):
index_map[number].append(index)
# Convert to iterators
iterators = {num: iter(indices) for num, indices in index_map.items()}
result = [next(iterators[number]) for number in nums1]
Pitfall 3: Assuming Order Preservation in Set.pop()
The Problem:
Some might expect set.pop()
to return elements in a specific order:
# Incorrect assumption # Expecting pop() to return indices in ascending order s = {0, 1, 2, 3} first = s.pop() # Might not be 0!
Why it matters:
- Sets are unordered in Python (though implementation details have changed in Python 3.7+)
pop()
returns an arbitrary element, not necessarily the "first" or "smallest"- If a specific mapping order is required (e.g., smallest available index), the current solution won't work
The Solution: If you need a specific order, use a different data structure:
# For smallest index first
index_map = defaultdict(list)
for index, number in enumerate(nums2):
index_map[number].append(index)
# Sort each list once (or use heapq for better performance)
for indices in index_map.values():
indices.sort(reverse=True) # Reverse so we can pop from end
result = [index_map[number].pop() for number in nums1]
Which of the following is a good use case for backtracking?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!