2295. Replace Elements in an Array
Problem Description
In this problem, we are given an array nums
which contains n
distinct positive integers, meaning all the elements in the array are unique and start from index 0. Additionally, we are provided with a set of m
operations, each representing a change we need to apply to the array. Each operation is a pair of integers where the first integer is a number that currently exists in the array and the second integer is the number we want to replace it with. The conditions are such that the first integer definitely exists in the array while the second is guaranteed not to be in the array before the operation is carried out. Our task is to perform all these operations on the array and return the final state of the array after all the replacements have been made.
Key Points:
- Each element in the array is unique.
- Each operation consists of replacing an existing element with a new one that is not already in the array.
The challenge is to apply these operations efficiently, keeping in mind that the naive approach of searching for each element to replace would lead to a less optimal solution.
Intuition
The intuition behind the solution is to optimize the search and replace process. If we try to look for the element to replace linearly every time, it would be inefficient, especially for a large array. Therefore, we use a hash map, in Python it's a dictionary, to keep track of the indices of the current elements in the array.
Here’s how we arrive at the solution step by step:
-
Create a hash map (dictionary) to map each value in the array to its index for constant time access. This allows us to quickly find where the replacement should occur without searching the entire array.
-
Iterate over the list of operations. For each operation:
- Find the index of the current value we need to replace (since it exists in the nums array as per the problem's guarantee).
- Replace the value with the new one in the array.
- Update the hash map by changing the mapping of the old value to the new value, essentially changing the key while keeping the value (which is the index) the same.
This way, we ensure that all the replacements can be done in linear time relative to the number of operations, leading to an efficient solution.
Solution Approach
The implementation of the solution follows a straightforward approach leveraging a dictionary data structure for fast lookups and updates to the nums
array based on the operations provided. Here's how the code brings the intuition to life:
-
Initialize a dictionary (hash map) named
d
. Use a dictionary comprehension to map each valuev
of the arraynums
to its corresponding indexi
. This is done in the form of{v: i for i, v in enumerate(nums)}
, which will allow constant-time access to the indices of elements we need to update. -
Iterate over all the given operations using a for loop. In each iteration, you are given a pair (a tuple in Python) containing two integers:
[a, b]
wherea
is the value you need to replace innums
, andb
is the value you're going to replace it with. -
Inside the loop, access the index of value
a
directly from the dictionaryd
– this gives us the exact position in thenums
array where replacement is to take place, thus avoiding a full array scan. The operationnums[d[a]]
accesses the element we want to replace and sets it tob
. -
Now, update the dictionary for the new value
b
to have the same index asa
had before. The operationd[b] = d[a]
ensures that going forward, you can now find the position ofb
just as easily as you could finda
. -
After the loop has processed all operations, we return the modified
nums
array, which now reflects all the replacements that have been made.
By using a hash map, the solution ensures that lookup and update operations are done in O(1)
time, making the overall time complexity of the solution O(m)
, where m
is the number of operations, as each operation involves a constant amount of work.
This approach is efficient and avoids the need for repeated array traversal, which would be necessary if we tried to find elements by value rather than by keeping track of their indices.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider an example to illustrate the solution approach described above. Suppose we have the following input:
The nums
array is [5, 3, 9, 7]
, and the list of operations is [(5, 10), (3, 15), (9, 20)]
. Let's walk through each step of the approach.
-
We initialize a dictionary named
d
that will map each number innums
to its index:{5: 0, 3: 1, 9: 2, 7: 3}
. -
We begin iterating over the list of operations:
-
The first operation is
(5, 10)
. We look up the index of5
ind
, which is0
. We replace the element at index0
in thenums
array with10
. Thenums
array now looks like[10, 3, 9, 7]
. We then updated
to reflect the change:{10: 0, 3: 1, 9: 2, 7: 3}
. -
The second operation is
(3, 15)
. We look up the index of3
ind
, which is1
. We replace the element at index1
in thenums
array with15
. Thenums
array now looks like[10, 15, 9, 7]
. We then updated
to reflect the change:{10: 0, 15: 1, 9: 2, 7: 3}
. -
The third operation is
(9, 20)
. We look up the index of9
ind
, which is2
. We replace the element at index2
in thenums
array with20
. Thenums
array now looks like[10, 15, 20, 7]
. We then updated
to reflect the change:{10: 0, 15: 1, 20: 2, 7: 3}
.
-
-
After completing all the operations, we end with the final state of the
nums
array, which is[10, 15, 20, 7]
.
Following this approach, we have efficiently performed all replacements without having to search through the nums
array multiple times, showcasing the benefits of maintaining a hash map to track indices of array elements.
Solution Implementation
1class Solution:
2 def arrayChange(self, nums: List[int], operations: List[List[int]]) -> List[int]:
3 # Create a dictionary "value_to_index" to track the indices
4 # of the elements in "nums" array
5 value_to_index = {value: index for index, value in enumerate(nums)}
6
7 # Iterate through each operation in "operations" list
8 for old_value, new_value in operations:
9 # Retrieve the index of the element we're going to change
10 idx = value_to_index[old_value]
11 # Update the element in "nums" at the index to the new_value
12 nums[idx] = new_value
13 # Update the "value_to_index" dictionary to reflect the change
14 # Now "new_value" points to the updated index
15 value_to_index[new_value] = idx
16
17 # After processing all operations, return the updated "nums" array
18 return nums
19
1class Solution {
2
3 // This method takes an array of integers and an array of operations
4 // and applies the operations to the array.
5 public int[] arrayChange(int[] nums, int[][] operations) {
6 // Create a HashMap to quickly find the index of each number in 'nums'.
7 Map<Integer, Integer> indexMap = new HashMap<>();
8
9 // Fill the map with the numbers' values as keys and their indices as values.
10 for (int i = 0; i < nums.length; ++i) {
11 indexMap.put(nums[i], i);
12 }
13
14 // Iterate through each operation in the operations array.
15 for (int[] operation : operations) {
16 // Extract 'from' and 'to' values from the current operation.
17 int fromValue = operation[0], toValue = operation[1];
18
19 // Get the index of the 'fromValue' number in the 'nums' array.
20 int indexToUpdate = indexMap.get(fromValue);
21
22 // Update the number at that index to the 'toValue'.
23 nums[indexToUpdate] = toValue;
24
25 // Update the map with the new value ('toValue') pointing to the same index.
26 indexMap.put(toValue, indexToUpdate);
27 }
28
29 // Return the modified 'nums' array with all operations applied.
30 return nums;
31 }
32}
33
1class Solution {
2public:
3 vector<int> arrayChange(vector<int>& nums, vector<vector<int>>& operations) {
4 // Create an unordered_map to keep track of each number's index in the array
5 unordered_map<int, int> indexMap;
6 for (int i = 0; i < nums.size(); ++i) {
7 indexMap[nums[i]] = i;
8 }
9
10 // Loop over operations
11 for (auto& operation : operations) {
12 // Extract the original and new values from the operation
13 int originalValue = operation[0];
14 int newValue = operation[1];
15
16 // Update the nums array at the index where the original value was found
17 nums[indexMap[originalValue]] = newValue;
18
19 // Update the indexMap to reflect the index of the new value
20 indexMap[newValue] = indexMap[originalValue];
21 }
22
23 // Return the modified nums array after all operations have been performed
24 return nums;
25 }
26};
27
1function arrayChange(inputArray: number[], operations: number[][]): number[] {
2 // Create a map to store the value and its corresponding index in the input array
3 const indexMap = new Map<number, number>();
4
5 // Populate the map with each number and its index
6 inputArray.forEach((value, index) => {
7 indexMap.set(value, index);
8 });
9
10 // Iterate through the operations
11 for (const [oldValue, newValue] of operations) {
12 // Get the index of the element to be changed (oldValue)
13 const indexToChange = indexMap.get(oldValue);
14
15 // If the index exists, proceed with the update
16 if (indexToChange !== undefined) {
17 // Update the inputArray at the specific index to the newValue
18 inputArray[indexToChange] = newValue;
19
20 // Update the indexMap with the newValue pointing to the same index
21 indexMap.set(newValue, indexToChange);
22 }
23 }
24
25 // Return the modified input array
26 return inputArray;
27}
28
Time and Space Complexity
Time Complexity
The time complexity of the code can be analyzed by looking at the two major steps in the function: the creation of the dictionary d
that maps current values to their indices, and the iteration over the operations
list to apply the value changes.
- Constructing the dictionary
d
takesO(n)
time, wheren
is the length of thenums
array. This is because it involves iterating over each element once. - Iterating over the
operations
list takesO(m)
time, wherem
is the number of operations because each operation involves a constant amount of work: updating a value in the list and updating a single entry in the dictionaryd
.
The total time complexity is the sum of these two parts: O(n + m)
.
Space Complexity
For space complexity, we consider the extra space used by the algorithm, not including the input and output.
- The extra space comes from the dictionary
d
that maps values to indices, which contains at mostn
key-value pairs, wheren
is the length ofnums
. Thus, the space complexity for the dictionary isO(n)
. - No additional space that grows with the input size is used during the iteration over
operations
.
Hence, the total space complexity is O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
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!