LeetCode 2295 - Replace Elements in an Array
The problem asks us to perform a series of replacement operations on an array of distinct integers. Specifically, we have an initial array nums containing n distinct positive integers. We are also given m operations, each consisting of a pair [oldValue, newValue].
Difficulty: 🟡 Medium
Topics: Array, Hash Table, Simulation
Solution
Problem Understanding
The problem asks us to perform a series of replacement operations on an array of distinct integers. Specifically, we have an initial array nums containing n distinct positive integers. We are also given m operations, each consisting of a pair [oldValue, newValue]. For each operation, we must locate the element oldValue in the array and replace it with newValue. The constraints guarantee that oldValue always exists in the array at the time of the operation and that newValue is not already present in the array, which simplifies handling duplicates.
The expected output is the final state of the array after applying all operations in order.
Key points from the constraints include the large size limit (n and m up to 10^5) and the requirement that all elements are distinct. This suggests that a naive approach of scanning the array linearly for each replacement (O(n * m)) would be inefficient. Edge cases to consider include minimal arrays (length 1), maximal arrays with consecutive replacements, and sequences where elements are swapped back and forth.
Approaches
Brute Force
A straightforward approach is to iterate through each operation and, for each [oldValue, newValue], scan the array nums to find the index of oldValue and replace it with newValue. This approach guarantees correctness because it directly follows the operation instructions. However, it is inefficient since finding the index requires O(n) for each of the m operations, giving a total time complexity of O(n * m), which is unacceptable for the maximum constraints (n, m <= 10^5).
Optimal
The key insight is to use a hash map (dictionary in Python, map in Go) to track the indices of the elements in nums. With this structure, each replacement operation can be performed in O(1) time by looking up the index of oldValue in the map and updating both the array and the map with newValue. This reduces the overall complexity to O(n + m), as we build the map initially and then perform each operation in constant time.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n * m) | O(1) | Directly scan array for each replacement |
| Optimal | O(n + m) | O(n) | Use hash map for fast index lookup and updates |
Algorithm Walkthrough
-
Initialize a hash map
value_to_indexwhere the key is the element fromnumsand the value is its index. -
Iterate over the array
numsand populatevalue_to_indexso each number points to its index innums. -
For each operation
[oldValue, newValue]inoperations, do the following: -
Retrieve the index of
oldValuefromvalue_to_index. -
Replace
nums[index]withnewValue. -
Remove
oldValuefromvalue_to_indexand insertnewValuewith the same index. -
Return the updated
numsarray.
Why it works: The invariant is that value_to_index always accurately reflects the current position of every number in nums. Since all numbers are distinct and the operations guarantee no duplicates, each replacement can be done in constant time without conflicts.
Python Solution
from typing import List
class Solution:
def arrayChange(self, nums: List[int], operations: List[List[int]]) -> List[int]:
# Step 1: Build a mapping from value to its index
value_to_index = {num: i for i, num in enumerate(nums)}
# Step 2: Apply each operation
for old_value, new_value in operations:
index = value_to_index.pop(old_value) # Get index and remove old_value
nums[index] = new_value # Update the array
value_to_index[new_value] = index # Add new_value to the map
return nums
The implementation first constructs the hash map to enable O(1) lookup of indices. Each operation updates the array and maintains the map in sync. Using pop ensures oldValue is removed while retrieving its index. Finally, the modified array is returned.
Go Solution
func arrayChange(nums []int, operations [][]int) []int {
// Step 1: Build a map from value to index
valueToIndex := make(map[int]int, len(nums))
for i, num := range nums {
valueToIndex[num] = i
}
// Step 2: Apply each operation
for _, op := range operations {
oldValue, newValue := op[0], op[1]
index := valueToIndex[oldValue]
nums[index] = newValue
delete(valueToIndex, oldValue)
valueToIndex[newValue] = index
}
return nums
}
In Go, the map is created with make for efficiency. delete is used to remove the old key. All other steps mirror the Python solution. Slice operations in Go directly modify the array, ensuring the map stays consistent.
Worked Examples
Example 1:
nums = [1,2,4,6], operations = [[1,3],[4,7],[6,1]]
| Step | Operation | Array State | Map State |
|---|---|---|---|
| 0 | - | [1,2,4,6] | {1:0, 2:1, 4:2, 6:3} |
| 1 | [1,3] | [3,2,4,6] | {2:1, 4:2, 6:3, 3:0} |
| 2 | [4,7] | [3,2,7,6] | {2:1, 6:3, 3:0, 7:2} |
| 3 | [6,1] | [3,2,7,1] | {2:1, 3:0, 7:2, 1:3} |
Final output: [3,2,7,1]
Example 2:
nums = [1,2], operations = [[1,3],[2,1],[3,2]]
| Step | Operation | Array State | Map State |
|---|---|---|---|
| 0 | - | [1,2] | {1:0, 2:1} |
| 1 | [1,3] | [3,2] | {2:1, 3:0} |
| 2 | [2,1] | [3,1] | {3:0, 1:1} |
| 3 | [3,2] | [2,1] | {2:0, 1:1} |
Final output: [2,1]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n + m) | O(n) to build the initial map, O(1) per operation for m operations |
| Space | O(n) | Hash map stores n elements for index lookup |
The algorithm scales linearly with the size of the array and number of operations, which is optimal given the constraints.
Test Cases
# Provided examples
assert Solution().arrayChange([1,2,4,6], [[1,3],[4,7],[6,1]]) == [3,2,7,1] # Example 1
assert Solution().arrayChange([1,2], [[1,3],[2,1],[3,2]]) == [2,1] # Example 2
# Edge cases
assert Solution().arrayChange([1], [[1,2]]) == [2] # Single element array
assert Solution().arrayChange([5,10], [[5,10],[10,5]]) == [10,5] # Swapping values
assert Solution().arrayChange([1,2,3], [[1,4],[2,5],[3,6]]) == [4,5,6] # Multiple sequential replacements
# Stress test
nums = list(range(1, 100001))
ops = [[i, i+100001] for i in range(1, 100001)]
assert Solution().arrayChange(nums, ops) == list(range(100002, 200002)) # Large input
| Test | Why |
|---|---|
| [1,2,4,6], [[1,3],[4,7],[6,1]] | Validates normal sequence of replacements |
| [1,2], [[1,3],[2,1],[3,2]] | Checks chain of swaps and replacements |
| [1], [[1,2]] | Tests single-element array |
| [5,10], [[5,10],[10,5]] | Validates swaps that could confuse naive implementations |
| [1,2,3], [[1,4],[2,5],[3,6]] | Multiple sequential replacements on all elements |
| large range 1..100000 | Stress test to ensure efficiency |
Edge Cases
- Single Element Array: When
numshas only one element, it is important that the code correctly replaces it without trying to access indices outside the array. The hash map handles this naturally.
2