2122. Recover the Original Array
Problem Description
Alice had an initial array arr
containing n
positive integers. To create variability, she decided to generate two new arrays, lower
and higher
, each also containing n
elements. She chose a positive integer k
and subtracted k
from each element in arr
to form lower
, and added k
to each element in arr
to form higher
. Thus, lower[i] = arr[i] - k
and higher[i] = arr[i] + k
.
Unfortunately, Alice lost all three arrays arr
, lower
, and higher
. She now only remembers a single array nums
that contains all the elements of lower
and higher
mingled together without any indication as to which element belongs to which array. The task is to help Alice by reconstructing the original array arr
.
The constraints are that nums
consists of 2n
integers with exactly n
integers from lower
and n
from higher
. We need to return any valid construction of arr
. It is important to remember that multiple valid arrays might exist, so any of them will be considered correct. Furthermore, the problem guarantees that there is at least one valid array arr
.
Intuition
Since the original array arr
is lost, and the elements in nums
cannot be directly distinguished as belonging to either lower
or higher
, we need to analyze the properties of numbers in lower
and higher
. Recognizing that each element in higher
is generated by adding the same fixed value k
to the respective elements in lower
, we can sort nums
to sequentially establish pairs of elements that could potentially correspond to the original and its modification by k
.
The first step in uncovering arr
is sorting nums
. After the sort, knowing that the smallest value must be from the lower
array and the next smallest that forms a valid pair must be from higher
, one can attempt to determine the value of k
. This value should be twice the chosen k
because it's the difference between counterpart elements from higher
and lower
.
For each potential k
(which is the difference between a pair of elements in nums
), we check if it's even and not zero. Since k
has to be a positive integer, this test eliminates invalid candidates.
Once a potential k
is found, we proceed to form the pairs. A boolean array, vis
, is used to track elements already used in pairs. If the right elements are not found for pairing, we know the current k
candidate is invalid, and we move to the next candidate.
The pairing logic relies on finding elements nums[l]
and nums[r]
where nums[r] - nums[l]
equals the difference d
we're exploring as 2k
. We keep appending the midpoint of these pairs (nums[l] + nums[r]) / 2
to the ans
list until either we run out of elements or can no longer find valid pairs, which indicates a complete arr
.
If a complete arr
is found where the number of found pairs equals n
, we return arr
. If not, we continue testing other differences until a valid array is constructed or all possibilities have been exhausted, in which case an empty list is returned.
Learn more about Sorting patterns.
Solution Approach
The implementation of the solution involves a few key steps that utilize algorithms and data structures effectively to reconstruct Alice's initial array arr
.
-
Sorting
nums
: The very first operation in the code is sorting thenums
array. This is a common preprocessing step in many problems, as it often simplifies the problem by ordering the elements. In our case, sorting is essential because it allows us to pair elements innums
that could represent an original and its counterpart modified byk
. -
Finding the potential
k
: The for-loop starts withi
at index 1, iterating through the sortednums
. Each iteration examines whethernums[i] - nums[0]
is a potential2k
(the actualk
multiplied by 2). Only even and non-zero differences are considered valid because we need a positive integerk
. -
Reconstructing
arr
: A boolean arrayvis
is created to keep track of which elements have already been paired. Initially, it marks the element corresponding to the current candidatek
(nums[i]
at the top of the loop) as used. The code maintains two pointers,l
andr
, which start searching for pairs from the beginning ofnums
. These pointers skip used elements tracked byvis
. -
Pairing elements using two pointers: The paired elements are chosen by verifying that
nums[r] - nums[l]
equalsd
(the current candidate2k
). For each such pair, the midpoint(nums[l] + nums[r]) / 2
, representing an element inarr
, is calculated and appended toans
. -
Handling edge cases and completing the array: The pairing process continues until it's no longer possible to pair elements according to the current
d
value. If a completearr
is reconstructed such that the number of pairs equalsn
(which is half the length ofnums
), thenans
is returned as a valid original array. -
Returning the result: If, during the pairing process, an inconsistency is found, the algorithm breaks out of the inner while loop, meaning the current
d
is not valid, and the for-loop continues with the next candidate. If the algorithm finds a correct sequence of pairs forans
, it is returned as a valid candidate. Otherwise, the pairing attempt will fail for all differencesd
, and the function will return an empty list, though this is guaranteed not to happen as per the problem statement.
The above approach takes advantage of sorted arrays, the two-pointer technique, and the boolean visitation array to implement an efficient and effective solution.
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 walk through the solution approach with a small example. Suppose Alice remembers the following mingled array nums
that has elements from both lower
and higher
arrays: nums = [3, 9, 5, 7]
.
According to the problem description and solution approach, we want to reconstruct the original array arr
. Follow these steps:
-
Sort
nums
: After sorting, the arraynums
becomes[3, 5, 7, 9]
. Sorting ensures that the smallest element, which must be from thelower
array, is at the beginning. -
Finding potential
k
: Starting from the second smallest number innums
, which is5
, we take the difference with the smallest number3
, resulting in2
. Since the difference must be2k
(andk
is a positive integer), valid potentialk
values are1
, the only number that when doubled gives2
. -
Reconstructing
arr
: Initializevis = [false, false, false, false]
since no elements have been paired yet. We will look for pairs that have a difference of2
(our potential2k
). -
Pairing elements using two pointers:
- We start with the first element
3
and look for an element that has a difference of2
. The next element is5
, and5 - 3 = 2
which is our2k
. We mark elements3
and5
as visited:vis = [true, true, false, false]
. - Now, we calculate the midpoint,
(3 + 5) / 2 = 4
, which is a candidate forarr
. Hence,arr = [4]
. - We continue to the next unvisited element which is
7
, and look for its pair. We find9
(9 - 7 = 2
). Nowvis = [true, true, true, true]
. - The midpoint of
7
and9
is(7 + 9) / 2 = 8
, which is added toarr
. Nowarr = [4, 8]
.
- We start with the first element
-
Completing the array: At this point, all elements in
nums
have been visited and paired correctly. There are two pairs, and this matchesn
(sincenums
is of length2n
andarr
of lengthn
). The process is complete, and we have successfully reconstructedarr
. -
Returning the result: The array
arr = [4, 8]
is returned as a valid construction of Alice's original array.
By following these steps, we have used the sorted array, the two-pointer technique, and a boolean array that tracks paired elements to effectively reconstruct the array arr
that Alice had before the arrays were lost.
Solution Implementation
1from typing import List
2
3class Solution:
4 def recoverArray(self, nums: List[int]) -> List[int]:
5 # Sort the input array in ascending order.
6 nums.sort()
7 n = len(nums)
8
9 # Try to find the difference between a pair of original and added numbers.
10 for i in range(1, n):
11 difference = nums[i] - nums[0]
12
13 # Ignore differences of zero or that are odd, since
14 # the original problem constraint requires that the difference is even.
15 if difference == 0 or difference % 2 == 1:
16 continue
17
18 # Create an array to keep track of visited numbers.
19 visited = [False] * n
20 visited[i] = True
21
22 # Initialize the array to be returned.
23 original_numbers = [(nums[0] + nums[i]) // 2]
24
25 # Initialize pointers for the smaller value (l)
26 # and the larger value (r) in a candidate pair.
27 l, r = 1, i + 1
28
29 # While there are more elements to process:
30 while r < n:
31 # Move the l pointer to the next unvisited element.
32 while l < n and visited[l]:
33 l += 1
34
35 # Move the r pointer to the next element
36 # that makes the difference exactly 'difference'.
37 while r < n and nums[r] - nums[l] < difference:
38 r += 1
39
40 # If r reaches the end or the difference is incorrect, break the loop.
41 if r == n or nums[r] - nums[l] > difference:
42 break
43
44 # If a valid pair is found, add to visited and original_numbers array.
45 visited[r] = True
46 original_numbers.append((nums[l] + nums[r]) // 2)
47
48 # Move both pointers to the next potential pair.
49 l, r = l + 1, r + 1
50
51 # If the size of the original_numbers is half the size of the input array,
52 # then we have found all pairs and can return the result.
53 if len(original_numbers) == n // 2:
54 return original_numbers
55
56 # If no valid configuration is found, return an empty array.
57 return []
58
59# Example use:
60# solution = Solution()
61# result = solution.recoverArray([1, 3, 4, 2])
62# print(result) # Output: The recovered array of original integers.
63
1import java.util.Arrays;
2import java.util.ArrayList;
3import java.util.List;
4
5class Solution {
6 public int[] recoverArray(int[] nums) {
7 // Sort the input array to ensure the order
8 Arrays.sort(nums);
9 // Use i to scan through the array, starting from index 1
10 for (int i = 1, length = nums.length; i < length; ++i) {
11 // Calculate the difference between the current element and the first one
12 int difference = nums[i] - nums[0];
13 // Skip if the difference is zero or odd
14 if (difference == 0 || difference % 2 == 1) {
15 continue;
16 }
17 // Create a boolean array to keep track of visited elements
18 boolean[] visited = new boolean[length];
19 visited[i] = true; // Mark the current element as visited
20 // Initialize a list to store the elements of the original array
21 List<Integer> tempArray = new ArrayList<>();
22 // Add the reconstructed element to the temp list
23 tempArray.add((nums[0] + nums[i]) >> 1);
24 // Use two pointers to traverse the array and try to reconstruct the original array
25 for (int l = 1, r = i + 1; r < length; ++l, ++r) {
26 // Advance the left pointer past any already visited elements
27 while (l < length && visited[l]) {
28 ++l;
29 }
30 // Advance the right pointer until the condition is met
31 while (r < length && nums[r] - nums[l] < difference) {
32 ++r;
33 }
34 // Break if the pointer has reached the end or condition is not met
35 if (r == length || nums[r] - nums[l] > difference) {
36 break;
37 }
38 // Mark the right element as visited and add the reconstructed element
39 visited[r] = true;
40 tempArray.add((nums[l] + nums[r]) >> 1);
41 }
42 // If we've added the correct number of elements, the array is recovered
43 if (tempArray.size() == (length >> 1)) {
44 // Convert the list to an array and return it
45 int[] ans = new int[tempArray.size()];
46 int idx = 0;
47 for (int elem : tempArray) {
48 ans[idx++] = elem;
49 }
50 return ans;
51 }
52 }
53 // If no array was recovered, return null.
54 return null;
55 }
56}
57
1#include <vector>
2#include <algorithm>
3
4class Solution {
5public:
6 vector<int> recoverArray(vector<int>& nums) {
7 // Sort the input array.
8 sort(nums.begin(), nums.end());
9
10 // Iterate through the array, trying to find the correct difference 'd'.
11 for (int i = 1, n = nums.size(); i < n; ++i) {
12 int diff = nums[i] - nums[0];
13
14 // Skip if the difference is zero or odd, since the problem assumes an even difference.
15 if (diff == 0 || diff % 2 == 1) continue;
16
17 // Keep a visited array to mark the elements that have been used.
18 vector<bool> visited(n, false);
19 visited[i] = true;
20
21 // This will store the original array.
22 vector<int> originalArray;
23 originalArray.push_back((nums[0] + nums[i]) / 2); // Add the first element.
24
25 // Use two pointers to recover the original array using the current difference 'diff'.
26 for (int left = 1, right = i + 1; right < n; ++left, ++right) {
27 // Find the next unvisited element for the left pointer.
28 while (left < n && visited[left]) ++left;
29
30 // Find the corresponding pair for the left element such that the difference is 'diff'.
31 while (right < n && nums[right] - nums[left] < diff) ++right;
32
33 // Break if there is no pair found or the difference is larger than 'diff'.
34 if (right == n || nums[right] - nums[left] > diff) break;
35
36 visited[right] = true; // Mark the right element as visited.
37
38 // Recover and add the original element to 'originalArray'.
39 originalArray.push_back((nums[left] + nums[right]) / 2);
40 }
41
42 // If we have successfully recovered the whole array, return it.
43 if (originalArray.size() == (n / 2)) return originalArray;
44 }
45
46 // Return an empty array if no valid solution is found.
47 return {};
48 }
49};
50
1function recoverArray(nums: number[]): number[] {
2 // Sort the input array.
3 nums.sort((a, b) => a - b);
4
5 // Iterate through the array, attempting to find the correct difference 'd'.
6 for (let i = 1; i < nums.length; i++) {
7 let diff = nums[i] - nums[0];
8
9 // Skip if the difference is zero or odd, since the difference should be even.
10 if (diff === 0 || diff % 2 === 1) {
11 continue;
12 }
13
14 // Initialize an array to keep track of visited elements.
15 let visited: boolean[] = new Array(nums.length).fill(false);
16 visited[i] = true;
17
18 // Initialize the array that will store the recovered original array.
19 let originalArray: number[] = [];
20 // Add the first element of the original array.
21 originalArray.push((nums[0] + nums[i]) / 2);
22
23 // Use two pointers to attempt to recover the original array using the current difference 'diff'.
24 for (let left = 1, right = i + 1; right < nums.length;) {
25 // Find the next unvisited element for the left pointer.
26 while (left < nums.length && visited[left]) {
27 left++;
28 }
29
30 // Find the corresponding pair for the left element that meets the difference 'diff'.
31 while (right < nums.length && nums[right] - nums[left] < diff) {
32 right++;
33 }
34
35 // If the pointers exceed bounds or the difference exceeds 'diff', break out.
36 if (right >= nums.length || nums[right] - nums[left] > diff) {
37 break;
38 }
39
40 // Mark the right element as visited.
41 visited[right] = true;
42
43 // Recover and add the next element to the 'originalArray'.
44 originalArray.push((nums[left] + nums[right]) / 2);
45 // Move to the next possible unvisited elements.
46 left++;
47 right++;
48 }
49
50 // If the original array was successfully recovered, return it.
51 if (originalArray.length === nums.length / 2) {
52 return originalArray;
53 }
54 }
55
56 // Return an empty array if no valid solution is found.
57 return [];
58}
59
Time and Space Complexity
Time Complexity
The time complexity of the algorithm can be analyzed as follows:
-
Sorting the
nums
array: Sorting an array of sizen
typically takesO(n log n)
time. -
The outer loop runs at most
n
times because it iterates through the sortednums
array starting from the second element. -
For each element in the outer loop, there are two inner loops (while loops) that could potentially run
n
times each in the worst case.- The first inner loop increments
l
until it finds an unvisited element. - The second inner loop increments
r
until it finds the pair element that satisfiesnums[r] - nums[l] == d
.
- The first inner loop increments
However, each element from the nums
array gets paired at most once due to the vis
array tracking the visited status. This means that in total, each inner loop contributes at maximum n
iterations across all iterations of the outer loop, not n
per outer loop iteration.
Thus, the time complexity is primarily dictated by the outer loop and the pairing process, leading to a complexity of O(n log n)
for sorting plus O(n)
for pairing, which simplifies to O(n log n)
for the entire algorithm.
Therefore, the overall time complexity of the code is O(n log n)
.
Space Complexity
The space complexity can be considered by analyzing the storage used by the algorithm:
-
The
vis
array: An array of sizen
used to keep track of the visited elements, which occupiesO(n)
space. -
The
ans
array: Potentially, this array could storen/2
elements when all the correct pairings are made (since pairs are formed), so it usesO(n)
space as well.
As such, the space complexity of the algorithm is determined by the additional arrays used for keeping track of visited elements and storing the answer, leading to O(n)
space complexity.
In conclusion, the space complexity is O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!