2295. Replace Elements in an Array

MediumArrayHash TableSimulation
Leetcode Link

Problem Description

You have an array nums containing n distinct positive integers (0-indexed). You need to perform m operations on this array, where each operation is defined in an operations array.

Each operation operations[i] contains two values:

  • operations[i][0]: the number to find and replace in nums
  • operations[i][1]: the new number to replace it with

The problem guarantees that:

  • The number operations[i][0] will always exist in nums when the i-th operation is performed
  • The number operations[i][1] will never already exist in nums when the i-th operation is performed

Your task is to apply all operations sequentially and return the final modified array.

Example:

If nums = [1, 2, 4, 6] and operations = [[1, 3], [4, 7], [6, 1]], the process would be:

  • Operation 1: Replace 1 with 3 → nums = [3, 2, 4, 6]
  • Operation 2: Replace 4 with 7 → nums = [3, 2, 7, 6]
  • Operation 3: Replace 6 with 1 → nums = [3, 2, 7, 1]

The final array [3, 2, 7, 1] would be returned.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to efficiently find where a specific value is located in the array to replace it. If we were to search through the entire array for each operation to find the value to replace, it would take O(n) time per operation, leading to O(m * n) total time complexity.

Instead, we can maintain a hash table that maps each value to its index in the array. This allows us to find any value's position in O(1) time.

Here's the thought process:

  1. Why a hash table? Since all numbers in nums are distinct, we can create a one-to-one mapping between values and their indices. This mapping allows instant lookup of where any value is located.

  2. How to handle replacements? When we replace value x with value y:

    • We know exactly where x is located (from our hash table: d[x])
    • We update the array at that position: nums[d[x]] = y
    • We need to update our hash table to reflect that y is now at that position: d[y] = d[x]
    • We don't need to remove x from the hash table since it will never be referenced again (the problem guarantees each value to be replaced exists only once and won't be replaced again)
  3. Why this works? The problem guarantees that when replacing x with y, y doesn't already exist in the array. This means we won't accidentally overwrite an existing mapping in our hash table. Also, once a value is replaced, it's gone from the array forever, so we don't need to worry about tracking multiple positions for the same value.

This approach reduces our time complexity from O(m * n) to O(n + m), where n is for building the initial hash table and m is for processing all operations.

Solution Implementation

1class Solution: 2 def arrayChange(self, nums: List[int], operations: List[List[int]]) -> List[int]: 3 # Create a dictionary mapping each value to its index in nums 4 # This allows O(1) lookup of where each value is located 5 value_to_index = {value: index for index, value in enumerate(nums)} 6 7 # Process each operation [old_value, new_value] 8 for old_value, new_value in operations: 9 # Get the index where old_value is currently located 10 index = value_to_index[old_value] 11 12 # Replace old_value with new_value at that index 13 nums[index] = new_value 14 15 # Update the dictionary: new_value now occupies the index 16 value_to_index[new_value] = index 17 18 # Note: We don't need to delete old_value from the dictionary 19 # since it won't be referenced again (problem guarantees uniqueness) 20 21 return nums 22
1class Solution { 2 public int[] arrayChange(int[] nums, int[][] operations) { 3 int arrayLength = nums.length; 4 5 // Create a map to store value -> index mapping for O(1) lookup 6 Map<Integer, Integer> valueToIndexMap = new HashMap<>(arrayLength); 7 8 // Initialize the map with original array values and their indices 9 for (int i = 0; i < arrayLength; ++i) { 10 valueToIndexMap.put(nums[i], i); 11 } 12 13 // Process each operation to replace values in the array 14 for (int[] operation : operations) { 15 int oldValue = operation[0]; 16 int newValue = operation[1]; 17 18 // Get the index of the value to be replaced 19 int index = valueToIndexMap.get(oldValue); 20 21 // Update the value in the array 22 nums[index] = newValue; 23 24 // Update the map: add new value mapping and remove old one 25 valueToIndexMap.put(newValue, index); 26 valueToIndexMap.remove(oldValue); 27 } 28 29 return nums; 30 } 31} 32
1class Solution { 2public: 3 vector<int> arrayChange(vector<int>& nums, vector<vector<int>>& operations) { 4 // Create a hash map to store the mapping from value to its index in nums 5 // This allows O(1) lookup of where each value is located 6 unordered_map<int, int> valueToIndex; 7 8 // Initialize the map with current values and their positions 9 for (int i = 0; i < nums.size(); ++i) { 10 valueToIndex[nums[i]] = i; 11 } 12 13 // Process each operation [oldValue, newValue] 14 for (auto& operation : operations) { 15 int oldValue = operation[0]; 16 int newValue = operation[1]; 17 18 // Find the index where oldValue is currently located 19 int index = valueToIndex[oldValue]; 20 21 // Replace oldValue with newValue at that index 22 nums[index] = newValue; 23 24 // Update the map: newValue is now at the index where oldValue was 25 valueToIndex[newValue] = index; 26 27 // Note: We don't need to remove oldValue from the map 28 // since it won't appear again (problem constraint) 29 } 30 31 return nums; 32 } 33}; 34
1/** 2 * Applies a series of replacement operations to an array 3 * @param nums - The original array of numbers 4 * @param operations - Array of [oldValue, newValue] pairs representing replacements 5 * @returns The modified array after all operations 6 */ 7function arrayChange(nums: number[], operations: number[][]): number[] { 8 // Create a map to track the current index of each value in the array 9 // Key: value, Value: index position 10 const valueToIndexMap: Map<number, number> = new Map( 11 nums.map((value, index) => [value, index]) 12 ); 13 14 // Process each operation to replace values 15 for (const [oldValue, newValue] of operations) { 16 // Get the index of the value to be replaced 17 const targetIndex = valueToIndexMap.get(oldValue)!; 18 19 // Update the value at the target index in the array 20 nums[targetIndex] = newValue; 21 22 // Update the map to reflect the new value at this index 23 valueToIndexMap.set(newValue, targetIndex); 24 25 // Note: We don't delete the old value from the map as it won't be referenced again 26 } 27 28 return nums; 29} 30

Solution Approach

The solution uses a hash table to track the position of each value in the array for efficient lookups and updates.

Step-by-step implementation:

  1. Build the position mapping:

    d = {x: i for i, x in enumerate(nums)}

    We create a dictionary d where each key is a value from nums and each value is its corresponding index. For example, if nums = [1, 2, 4, 6], then d = {1: 0, 2: 1, 4: 2, 6: 3}.

  2. Process each operation:

    for x, y in operations:  nums[d[x]] = y  d[y] = d[x]

    For each operation [x, y]:

    • d[x] gives us the index where value x is located
    • nums[d[x]] = y replaces the value at that index with y
    • d[y] = d[x] updates our hash table to record that y is now at the position where x used to be
  3. Return the modified array:

    return nums

Example walkthrough:

Let's trace through nums = [1, 2, 4, 6] and operations = [[1, 3], [4, 7], [6, 1]]:

  • Initial: d = {1: 0, 2: 1, 4: 2, 6: 3}
  • Operation [1, 3]: Replace 1 with 3
    • nums[d[1]] = nums[0] = 3nums = [3, 2, 4, 6]
    • d[3] = d[1] = 0d = {1: 0, 2: 1, 4: 2, 6: 3, 3: 0}
  • Operation [4, 7]: Replace 4 with 7
    • nums[d[4]] = nums[2] = 7nums = [3, 2, 7, 6]
    • d[7] = d[4] = 2d = {1: 0, 2: 1, 4: 2, 6: 3, 3: 0, 7: 2}
  • Operation [6, 1]: Replace 6 with 1
    • nums[d[6]] = nums[3] = 1nums = [3, 2, 7, 1]
    • d[1] = d[6] = 3d = {1: 3, 2: 1, 4: 2, 6: 3, 3: 0, 7: 2}

The final array [3, 2, 7, 1] is returned.

Time Complexity: O(n + m) where n is the length of nums and m is the number of operations Space Complexity: O(n) for the hash table

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a smaller example to illustrate the solution approach.

Given:

  • nums = [5, 8, 3]
  • operations = [[5, 2], [3, 9]]

Step 1: Build the position mapping

We create a hash table that maps each value to its index:

d = {5: 0, 8: 1, 3: 2}

This tells us that:

  • Value 5 is at index 0
  • Value 8 is at index 1
  • Value 3 is at index 2

Step 2: Process first operation [5, 2]

We need to replace 5 with 2:

  • Look up where 5 is located: d[5] = 0
  • Replace the value at index 0: nums[0] = 2
    • Array becomes: [2, 8, 3]
  • Update hash table to record that 2 is now at index 0: d[2] = 0
    • Hash table becomes: {5: 0, 8: 1, 3: 2, 2: 0}

Step 3: Process second operation [3, 9]

We need to replace 3 with 9:

  • Look up where 3 is located: d[3] = 2
  • Replace the value at index 2: nums[2] = 9
    • Array becomes: [2, 8, 9]
  • Update hash table to record that 9 is now at index 2: d[9] = 2
    • Hash table becomes: {5: 0, 8: 1, 3: 2, 2: 0, 9: 2}

Final Result: [2, 8, 9]

The key insight is that by maintaining the hash table, we can find any value's position in O(1) time instead of searching through the entire array. When we replace a value, we update both the array and the hash table to keep them synchronized. The old values remain in the hash table but are never accessed again since the problem guarantees each value is replaced at most once.

Time and Space Complexity

Time Complexity: O(n + m)

The algorithm consists of two main parts:

  1. Building the dictionary d with a comprehension that iterates through all n elements in nums - this takes O(n) time
  2. Processing all m operations in the for loop, where each operation involves:
    • Dictionary lookup d[x] - O(1) time
    • Array assignment nums[d[x]] = y - O(1) time
    • Dictionary assignment d[y] = d[x] - O(1) time

Since we perform O(1) operations for each of the m operations, the total time for the second part is O(m).

Therefore, the overall time complexity is O(n + m), where n is the length of the array nums and m is the length of the operations array.

Space Complexity: O(n)

The algorithm uses a dictionary d that stores a mapping for each element in nums to its index. Initially, this dictionary contains n key-value pairs. During the operations, we only update existing entries or add new keys for values that replace existing ones, but the dictionary size remains bounded by the number of unique values that have appeared in the array, which is at most O(n) additional entries. Thus, the space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Attempting to Find Value's Index Without Hash Table

The Problem: A common mistake is trying to locate each value using linear search instead of maintaining a hash table:

# Inefficient approach - DON'T DO THIS for old_value, new_value in operations:  index = nums.index(old_value) # O(n) operation!  nums[index] = new_value

This approach has O(m*n) time complexity, which can cause timeout for large inputs.

The Solution: Always maintain a hash table for O(1) lookups:

value_to_index = {value: index for index, value in enumerate(nums)} for old_value, new_value in operations:  index = value_to_index[old_value] # O(1) operation  nums[index] = new_value  value_to_index[new_value] = index

Pitfall 2: Forgetting to Update the Hash Table

The Problem: After replacing a value in the array, forgetting to update the hash table mapping:

# Incorrect - hash table not updated for old_value, new_value in operations:  index = value_to_index[old_value]  nums[index] = new_value  # Missing: value_to_index[new_value] = index

This causes subsequent operations to fail when trying to find values that were introduced in previous operations.

The Solution: Always update the hash table immediately after modifying the array:

for old_value, new_value in operations:  index = value_to_index[old_value]  nums[index] = new_value  value_to_index[new_value] = index # Critical update!

Pitfall 3: Deleting Old Values from Hash Table

The Problem: Unnecessarily deleting old values from the hash table:

# Unnecessary deletion for old_value, new_value in operations:  index = value_to_index[old_value]  nums[index] = new_value  value_to_index[new_value] = index  del value_to_index[old_value] # Not needed!

While this doesn't break the solution, it adds unnecessary overhead. The problem guarantees that old values won't be referenced again.

The Solution: Simply leave old entries in the hash table - they won't affect correctness:

for old_value, new_value in operations:  index = value_to_index[old_value]  nums[index] = new_value  value_to_index[new_value] = index  # No deletion needed - old_value won't be accessed again
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More