2122. Recover the Original Array
Problem Description
Alice originally had an array arr
of n
positive integers. She picked a positive integer k
and created two new arrays from it:
lower[i] = arr[i] - k
for each element at indexi
higher[i] = arr[i] + k
for each element at indexi
Now Alice has lost all three arrays (the original arr
, lower
, and higher
). However, she still remembers all the numbers that were in lower
and higher
combined, but she doesn't know which number came from which array.
You're given an array nums
containing 2n
integers - these are all the values from both lower
and higher
mixed together. Your task is to recover the original array arr
.
Key observations:
- The
nums
array has exactly2n
elements (n fromlower
, n fromhigher
) - For each original value
arr[i]
, there existsarr[i] - k
inlower
andarr[i] + k
inhigher
- This means for each original value, the difference between its corresponding values in
nums
is2k
- The original value
arr[i]
is the average of its two corresponding values:arr[i] = (lower[i] + higher[i]) / 2
The solution tries different possible values of 2k
by checking pairs of elements in the sorted nums
array. For each valid 2k
value (must be even and positive), it attempts to pair up elements that differ by exactly 2k
. If exactly n
valid pairs can be formed, the average of each pair gives us the original array values.
If multiple valid arrays exist, any one of them can be returned. The problem guarantees at least one valid solution exists.
Intuition
The key insight is understanding the relationship between the original array and the mixed array we're given. Since each original value arr[i]
produces two values (arr[i] - k
and arr[i] + k
), these two values are always exactly 2k
apart.
Think of it this way: if we sort the mixed array nums
, the smallest element must be some arr[i] - k
(since subtracting k
always gives a smaller value). This smallest element needs a partner that is 2k
larger to form a valid pair.
But here's the challenge - we don't know what k
is! So how do we find it?
The breakthrough comes from realizing that the smallest element in nums
must belong to the lower
array (it's some original value minus k
). Its corresponding element from the higher
array must be somewhere else in nums
, exactly 2k
units away.
Since we don't know which element is its partner, we can try pairing the smallest element with each other element in the array. The difference between them would give us a candidate value for 2k
. For this to be valid:
- The difference must be even (since
2k
is even whenk
is an integer) - The difference must be positive
Once we have a candidate 2k
, we can verify if it's correct by trying to pair up all elements in nums
with this fixed difference. We use a greedy approach: always try to pair the smallest unpaired element with another element that's exactly 2k
larger.
If we can successfully create exactly n
pairs where each pair has a difference of 2k
, then we've found the correct value of k
. The original array values are simply the midpoints of each pair: (lower_value + higher_value) / 2 = (arr[i] - k + arr[i] + k) / 2 = arr[i]
.
The beauty of this approach is that we systematically try all possible values of 2k
by considering each element as a potential partner for the smallest element, guaranteeing we'll find the correct solution.
Learn more about Two Pointers and Sorting patterns.
Solution Approach
The implementation follows a systematic approach to find the correct value of k
and reconstruct the original array:
-
Sort the input array: First, we sort
nums
to make it easier to find pairs with a specific difference. After sorting, we know the smallest element must be from thelower
array. -
Try different values of 2k: We iterate through each element
nums[i]
(starting from index 1) and calculate the differenced = nums[i] - nums[0]
. This difference represents a candidate value for2k
.- Skip if
d == 0
(same element, can't be a valid pair) - Skip if
d % 2 == 1
(odd difference meansk
wouldn't be an integer)
- Skip if
-
Attempt to pair elements with the current difference: For each valid candidate
d
:- Use a boolean array
vis
to track which elements have been paired - Mark
nums[i]
as visited (it's paired withnums[0]
) - Calculate the first element of the original array:
(nums[0] + nums[i]) >> 1
(right shift by 1 is equivalent to dividing by 2)
- Use a boolean array
-
Greedy pairing process: Use two pointers
l
andr
to find pairs:l
starts at 1 (index 0 is already paired)r
starts ati + 1
- For each unpaired element at position
l
:- Skip already visited elements by advancing
l
- Move
r
forward until we find an element wherenums[r] - nums[l] >= d
- If
nums[r] - nums[l] == d
, we found a valid pair:- Mark
nums[r]
as visited - Add the midpoint
(nums[l] + nums[r]) >> 1
to the answer - Move both pointers forward
- Mark
- If
nums[r] - nums[l] > d
or we reach the end of the array, this value ofd
doesn't work
- Skip already visited elements by advancing
-
Verify the solution: If we successfully create exactly
n >> 1
pairs (which isn/2
), we've found the correct value ofk
and reconstructed the original array. Return the answer. -
Continue if unsuccessful: If the current value of
d
doesn't yield a complete solution, try the next candidate.
The algorithm efficiently explores all possible values of 2k
in O(nยฒ)
time in the worst case, where for each of the O(n)
candidates, we perform an O(n)
pairing operation. The space complexity is O(n)
for storing the visited array and the answer.
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 concrete example to understand how the solution works.
Example: nums = [1, 5, 3, 7]
We need to find the original array of size 2 that Alice had. Let's trace through the algorithm:
Step 1: Sort the array
- Sorted
nums = [1, 3, 5, 7]
- We know
nums[0] = 1
must be from thelower
array (somearr[i] - k
)
Step 2: Try different values of 2k
Attempt 1: Pair nums[0] = 1
with nums[1] = 3
- Difference
d = 3 - 1 = 2
(even, valid candidate for2k
) - This means
k = 1
- Mark indices 0 and 1 as paired
- First element of original array:
(1 + 3) / 2 = 2
- Now try to pair remaining elements:
nums[2] = 5
andnums[3] = 7
- Check if
7 - 5 = 2
โ (equals ourd
) - Second element:
(5 + 7) / 2 = 6
- Successfully paired all elements!
- Answer:
[2, 6]
Let's verify this is correct:
- Original array:
[2, 6]
withk = 1
- Lower array:
[2-1, 6-1] = [1, 5]
- Higher array:
[2+1, 6+1] = [3, 7]
- Combined:
[1, 5, 3, 7]
โ matches our input
What if Attempt 1 had failed?
Let's see what would happen if we tried pairing nums[0] = 1
with nums[2] = 5
:
- Difference
d = 5 - 1 = 4
(even, valid candidate for2k
) - This means
k = 2
- Mark indices 0 and 2 as paired
- First element:
(1 + 5) / 2 = 3
- Try to pair remaining:
nums[1] = 3
andnums[3] = 7
- Check if
7 - 3 = 4
โ (equals ourd
) - Second element:
(3 + 7) / 2 = 5
- Alternative answer:
[3, 5]
Verification:
- Original array:
[3, 5]
withk = 2
- Lower array:
[3-2, 5-2] = [1, 3]
- Higher array:
[3+2, 5+2] = [5, 7]
- Combined:
[1, 3, 5, 7]
โ also matches!
This example shows that multiple valid solutions can exist, and the algorithm will find the first valid one based on the order it tries different values of 2k
.
Solution Implementation
1class Solution:
2 def recoverArray(self, nums: List[int]) -> List[int]:
3 # Sort the input array to facilitate pairing
4 nums.sort()
5 n = len(nums)
6
7 # Try different possible values of 2k by pairing nums[0] with each other element
8 for i in range(1, n):
9 # Calculate the difference (which should be 2k)
10 difference = nums[i] - nums[0]
11
12 # Skip invalid cases: difference must be positive and even
13 if difference == 0 or difference % 2 == 1:
14 continue
15
16 # Track which elements have been used in pairs
17 visited = [False] * n
18 visited[i] = True
19
20 # Calculate the first element of the original array
21 # (nums[0] + nums[i]) / 2 = (arr[j] - k + arr[j] + k) / 2 = arr[j]
22 result = [(nums[0] + nums[i]) >> 1]
23
24 # Two pointers to find matching pairs
25 left_pointer = 1
26 right_pointer = i + 1
27
28 # Try to pair remaining elements
29 while right_pointer < n:
30 # Skip already visited elements on the left
31 while left_pointer < n and visited[left_pointer]:
32 left_pointer += 1
33
34 # Find the matching element on the right with exact difference
35 while right_pointer < n and nums[right_pointer] - nums[left_pointer] < difference:
36 right_pointer += 1
37
38 # If no valid pair found, this k value doesn't work
39 if right_pointer == n or nums[right_pointer] - nums[left_pointer] > difference:
40 break
41
42 # Mark the right element as used and add the original value to result
43 visited[right_pointer] = True
44 result.append((nums[left_pointer] + nums[right_pointer]) >> 1)
45
46 # Move both pointers forward
47 left_pointer += 1
48 right_pointer += 1
49
50 # If we successfully paired all elements, return the result
51 if len(result) == (n >> 1):
52 return result
53
54 # No valid k value found (shouldn't happen with valid input)
55 return []
56
1class Solution {
2 public int[] recoverArray(int[] nums) {
3 // Sort the input array to make pairing easier
4 Arrays.sort(nums);
5
6 // Try different possible values of k by pairing nums[0] with each subsequent element
7 for (int i = 1, n = nums.length; i < n; i++) {
8 // Calculate the difference between current element and the smallest element
9 int difference = nums[i] - nums[0];
10
11 // Skip if difference is 0 (same value) or odd (k must be integer)
12 if (difference == 0 || difference % 2 == 1) {
13 continue;
14 }
15
16 // Track which elements have been used in pairs
17 boolean[] visited = new boolean[n];
18 visited[i] = true;
19
20 // Store the original array values (before adding/subtracting k)
21 List<Integer> originalValues = new ArrayList<>();
22 originalValues.add((nums[0] + nums[i]) >> 1); // Average of the pair
23
24 // Try to pair remaining elements with the same difference
25 int leftIndex = 1;
26 int rightIndex = i + 1;
27
28 while (rightIndex < n) {
29 // Skip already visited elements on the left
30 while (leftIndex < n && visited[leftIndex]) {
31 leftIndex++;
32 }
33
34 // Find the matching element on the right with exact difference
35 while (rightIndex < n && nums[rightIndex] - nums[leftIndex] < difference) {
36 rightIndex++;
37 }
38
39 // If no valid pair found, this k value doesn't work
40 if (rightIndex == n || nums[rightIndex] - nums[leftIndex] > difference) {
41 break;
42 }
43
44 // Mark the right element as used and store the original value
45 visited[rightIndex] = true;
46 originalValues.add((nums[leftIndex] + nums[rightIndex]) >> 1);
47
48 // Move to next pair
49 leftIndex++;
50 rightIndex++;
51 }
52
53 // Check if we successfully paired all elements (n/2 pairs)
54 if (originalValues.size() == (n >> 1)) {
55 // Convert list to array and return
56 int[] result = new int[originalValues.size()];
57 int index = 0;
58 for (int value : originalValues) {
59 result[index++] = value;
60 }
61 return result;
62 }
63 }
64
65 // No valid k found (shouldn't happen with valid input)
66 return null;
67 }
68}
69
1class Solution {
2public:
3 vector<int> recoverArray(vector<int>& nums) {
4 // Sort the input array to pair elements systematically
5 sort(nums.begin(), nums.end());
6
7 int n = nums.size();
8
9 // Try different possible values of k by pairing nums[0] with each element
10 for (int i = 1; i < n; ++i) {
11 // Calculate the difference between current element and the smallest element
12 int difference = nums[i] - nums[0];
13
14 // Skip if difference is 0 or odd (k must be positive and difference must be even)
15 if (difference == 0 || difference % 2 == 1) {
16 continue;
17 }
18
19 // Track which elements have been used in pairs
20 vector<bool> used(n, false);
21 used[i] = true;
22
23 // Store the recovered original array elements
24 vector<int> result;
25
26 // Add the first recovered element (midpoint of nums[0] and nums[i])
27 result.push_back((nums[0] + nums[i]) >> 1);
28
29 // Try to pair remaining elements with the same difference
30 int leftPtr = 1;
31 int rightPtr = i + 1;
32
33 while (rightPtr < n) {
34 // Skip already used elements on the left
35 while (leftPtr < n && used[leftPtr]) {
36 ++leftPtr;
37 }
38
39 // Find the matching element on the right with the required difference
40 while (rightPtr < n && nums[rightPtr] - nums[leftPtr] < difference) {
41 ++rightPtr;
42 }
43
44 // If no valid pair found, this k value doesn't work
45 if (rightPtr == n || nums[rightPtr] - nums[leftPtr] > difference) {
46 break;
47 }
48
49 // Mark the right element as used and add the recovered element
50 used[rightPtr] = true;
51 result.push_back((nums[leftPtr] + nums[rightPtr]) >> 1);
52
53 // Move to next pair
54 ++leftPtr;
55 ++rightPtr;
56 }
57
58 // If we successfully recovered half the elements, we found the answer
59 if (result.size() == (n >> 1)) {
60 return result;
61 }
62 }
63
64 // No valid k value found
65 return {};
66 }
67};
68
1function recoverArray(nums: number[]): number[] {
2 // Sort the input array to pair elements systematically
3 nums.sort((a, b) => a - b);
4
5 const n = nums.length;
6
7 // Try different possible values of k by pairing nums[0] with each element
8 for (let i = 1; i < n; i++) {
9 // Calculate the difference between current element and the smallest element
10 const difference = nums[i] - nums[0];
11
12 // Skip if difference is 0 or odd (k must be positive and difference must be even)
13 if (difference === 0 || difference % 2 === 1) {
14 continue;
15 }
16
17 // Track which elements have been used in pairs
18 const used: boolean[] = new Array(n).fill(false);
19 used[i] = true;
20
21 // Store the recovered original array elements
22 const result: number[] = [];
23
24 // Add the first recovered element (midpoint of nums[0] and nums[i])
25 result.push((nums[0] + nums[i]) >> 1);
26
27 // Try to pair remaining elements with the same difference
28 let leftPointer = 1;
29 let rightPointer = i + 1;
30
31 while (rightPointer < n) {
32 // Skip already used elements on the left
33 while (leftPointer < n && used[leftPointer]) {
34 leftPointer++;
35 }
36
37 // Find the matching element on the right with the required difference
38 while (rightPointer < n && nums[rightPointer] - nums[leftPointer] < difference) {
39 rightPointer++;
40 }
41
42 // If no valid pair found, this k value doesn't work
43 if (rightPointer === n || nums[rightPointer] - nums[leftPointer] > difference) {
44 break;
45 }
46
47 // Mark the right element as used and add the recovered element
48 used[rightPointer] = true;
49 result.push((nums[leftPointer] + nums[rightPointer]) >> 1);
50
51 // Move to next pair
52 leftPointer++;
53 rightPointer++;
54 }
55
56 // If we successfully recovered half the elements, we found the answer
57 if (result.length === (n >> 1)) {
58 return result;
59 }
60 }
61
62 // No valid k value found
63 return [];
64}
65
Time and Space Complexity
Time Complexity: O(nยฒ)
The analysis breaks down as follows:
- The initial sorting takes
O(n log n)
time - The outer loop runs at most
n-1
times (iterating through possible values ofd
) - For each valid difference
d
, the inner while loop processes each element at most once- The
l
pointer traverses from index 1 to n, incrementing through the array - The
r
pointer also traverses fromi+1
to n - Both pointers move forward only, never backward
- This gives
O(n)
for each iteration of the outer loop
- The
- In the worst case, we might try all possible differences before finding the valid one
- Total time complexity:
O(n log n) + O(n) ร O(n) = O(nยฒ)
Space Complexity: O(n)
The space usage consists of:
- The
vis
boolean array of sizen
:O(n)
- The
ans
array which stores at mostn/2
elements:O(n/2) = O(n)
- A few integer variables (
l
,r
,d
, etc.):O(1)
- Total space complexity:
O(n)
Note that the sorting operation might use O(log n)
additional space depending on the implementation (e.g., quicksort's recursion stack), but the dominant factor remains O(n)
from the auxiliary arrays.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Pairing Logic - Not Resetting the Right Pointer
One of the most common mistakes is not properly managing the right pointer during the pairing process. Many implementations incorrectly continue from where the right pointer left off for the next left element, which can miss valid pairs.
Problematic Code:
# WRONG: right_pointer continues from previous position left_pointer = 1 right_pointer = i + 1 while right_pointer < n: while left_pointer < n and visited[left_pointer]: left_pointer += 1 # right_pointer doesn't reset, might skip valid pairs while right_pointer < n and nums[right_pointer] - nums[left_pointer] < difference: right_pointer += 1
Solution: The right pointer should start searching from positions after the current left pointer to ensure we don't miss valid pairs due to visited elements:
while left_pointer < n: # Skip visited elements while left_pointer < n and visited[left_pointer]: left_pointer += 1 if left_pointer >= n: break # Reset right_pointer to search from left_pointer + 1 right_pointer = left_pointer + 1 # Find matching pair while right_pointer < n and (visited[right_pointer] or nums[right_pointer] - nums[left_pointer] < difference): right_pointer += 1
2. Off-by-One Error in Result Array Size Check
Another pitfall is incorrectly checking if we've found all pairs. Since we have 2n
elements that should form n
pairs, the result array should have exactly n
elements.
Problematic Code:
# WRONG: Checking for n pairs instead of n/2
if len(result) == n: # This would expect 2n elements in result
return result
Solution:
# CORRECT: Check for n/2 elements (since n = len(nums) = 2 * original_array_size)
if len(result) == (n >> 1): # or n // 2
return result
3. Not Handling Integer Division Properly
When calculating the original array values, ensure proper integer division:
Problematic Code:
# Potential floating point issues result.append((nums[left] + nums[right]) / 2)
Solution:
# Use integer division or bit shift for guaranteed integer result result.append((nums[left] + nums[right]) >> 1) # or // 2
4. Forgetting to Mark Both Elements in Initial Pair
When pairing nums[0]
with nums[i]
, forgetting to mark nums[0]
as implicitly used can lead to incorrect pairing attempts:
Enhanced Solution with Explicit Tracking:
visited = [False] * n visited[0] = True # Explicitly mark nums[0] as used visited[i] = True # Mark nums[i] as used
While the current implementation works because it starts left_pointer
at 1, explicitly marking both elements makes the logic clearer and prevents bugs if the code is modified later.
What are the most two important steps in writing a depth first search function? (Select 2)
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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
Want a Structured Path to Master System Design Too? Donโt Miss This!