954. Array of Doubled Pairs
Problem Description
You are given an integer array arr
of even length. Your task is to determine if it's possible to reorder the array such that for every pair of consecutive elements at positions 2*i
and 2*i+1
, the second element is exactly double the first element.
More specifically, after reordering, the array should satisfy: arr[2*i+1] = 2 * arr[2*i]
for every valid index i
where 0 <= i < len(arr)/2
.
The function should return true
if such a reordering is possible, and false
otherwise.
For example:
- If
arr = [3, 1, 3, 6]
, we can reorder it to[3, 6, 1, 2]
which doesn't work, or[1, 2, 3, 6]
which also doesn't work. Actually, we need pairs where each second element is double the first. We could arrange it as[3, 6, 1, 2]
where6 = 2*3
and2 = 2*1
, so this would returntrue
. - If
arr = [4, -2, 2, -4]
, we can reorder it to[-2, -4, 2, 4]
where-4 = 2*(-2)
and4 = 2*2
, so this would returntrue
.
The key insight is that we need to pair up elements where one element is exactly double the other, and we must be able to use all elements in the array exactly once in such pairs.
Intuition
The core idea is that we need to form pairs where one element is exactly double the other. Think of it like a matching problem - for each number x
, we need to find its double 2x
to form a valid pair.
Let's think about which elements can be paired together. For a positive number x
, we need to pair it with 2x
. For a negative number x
, we also need to pair it with 2x
(which will also be negative). For example, -3
pairs with -6
.
The key insight is to process numbers in a specific order. If we process them by their absolute values (smallest absolute value first), we can avoid conflicts. Why? Consider this:
- If we have numbers like
2, 4, 8
, we want to pair2
with4
first, not4
with8
. This is because2
can only pair with4
, but4
could potentially pair with either2
(as the double) or8
(as the half). - By processing from smallest absolute value, we ensure that when we process a number
x
, we're looking for2x
, notx/2
. This eliminates ambiguity.
For the frequency counting approach: if a number x
appears freq[x]
times, then 2x
must appear at least freq[x]
times to form valid pairs. After pairing all occurrences of x
with 2x
, we reduce the count of 2x
by freq[x]
.
Special case for zero: Zero can only pair with itself (0 * 2 = 0
), so if we have an odd number of zeros, it's impossible to form valid pairs.
By sorting by absolute value and greedily pairing each number with its double, we can determine if a valid reordering exists. If at any point we can't find enough doubles for a number, we know it's impossible.
Solution Approach
The solution uses a frequency counter and greedy pairing strategy:
-
Count frequencies: First, we create a frequency map using
Counter(arr)
to track how many times each number appears in the array. -
Handle zeros: Check if the count of zeros is odd with
freq[0] & 1
. If it's odd, returnFalse
immediately since zeros can only pair with themselves, requiring an even count. -
Sort by absolute value: We sort the keys of the frequency map by their absolute values using
sorted(freq, key=abs)
. This ensures we process numbers from smallest to largest absolute value. -
Greedy pairing: For each number
x
in our sorted order:- We need to pair all
freq[x]
occurrences ofx
with2x
- Check if we have enough doubles:
freq[x << 1] < freq[x]
(using left shiftx << 1
which equals2 * x
) - If we don't have enough doubles, return
False
- Otherwise, reduce the frequency of
2x
byfreq[x]
since those are now paired:freq[x << 1] -= freq[x]
- We need to pair all
-
Return result: If we successfully process all numbers without running out of doubles, return
True
.
The algorithm works because:
- By processing from smallest absolute value, when we encounter
x
, we always pair it with2x
(never withx/2
) - This avoids the ambiguity of whether a number should be the smaller or larger element in a pair
- The greedy approach is optimal because each number must be paired exactly once
- Time complexity:
O(n log n)
for sorting - Space complexity:
O(n)
for the frequency counter
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 the example arr = [4, -2, 2, -4]
:
Step 1: Count frequencies
freq = {4: 1, -2: 1, 2: 1, -4: 1}
Step 2: Check zeros
- No zeros in the array, so we continue.
Step 3: Sort by absolute value
- Sorted keys:
[-2, 2, 4, -4]
(both -2 and 2 have absolute value 2, then 4 and -4 have absolute value 4)
Step 4: Process each number greedily
Processing -2
:
- Need to pair 1 occurrence of
-2
with2*(-2) = -4
- Check: Do we have at least 1 occurrence of
-4
? Yes,freq[-4] = 1
- Pair them:
freq[-4] = 1 - 1 = 0
- Current pairs:
[-2, -4]
Processing 2
:
- Need to pair 1 occurrence of
2
with2*2 = 4
- Check: Do we have at least 1 occurrence of
4
? Yes,freq[4] = 1
- Pair them:
freq[4] = 1 - 1 = 0
- Current pairs:
[-2, -4], [2, 4]
Processing 4
:
- Need to pair 0 occurrences (since
freq[4] = 0
now after being paired) - Skip, already paired as a double
Processing -4
:
- Need to pair 0 occurrences (since
freq[-4] = 0
now after being paired) - Skip, already paired as a double
Step 5: Success!
All numbers have been successfully paired. The valid reordering would be [-2, -4, 2, 4]
.
Let's verify:
- Position 0,1:
-4 = 2 * (-2)
โ - Position 2,3:
4 = 2 * 2
โ
Return True
.
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def canReorderDoubled(self, arr: List[int]) -> bool:
6 # Count frequency of each element in the array
7 frequency_map = Counter(arr)
8
9 # Check if we have an odd number of zeros
10 # Since 0 * 2 = 0, zeros can only pair with themselves
11 # So we need an even count of zeros
12 if frequency_map[0] % 2 == 1:
13 return False
14
15 # Sort elements by their absolute value
16 # This ensures we process smaller absolute values first
17 # For negative numbers: -4 needs -2, so we process -2 first
18 # For positive numbers: 2 needs 4, so we process 2 first
19 for current_value in sorted(frequency_map, key=abs):
20 # Calculate the double of current value
21 double_value = current_value * 2
22
23 # Check if we have enough doubles to pair with current values
24 if frequency_map[double_value] < frequency_map[current_value]:
25 return False
26
27 # Use up the doubles by pairing them with current values
28 frequency_map[double_value] -= frequency_map[current_value]
29
30 # If all elements can be paired successfully, return True
31 return True
32
1class Solution {
2 public boolean canReorderDoubled(int[] arr) {
3 // Create a frequency map to count occurrences of each number
4 Map<Integer, Integer> frequencyMap = new HashMap<>();
5 for (int number : arr) {
6 frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1);
7 }
8
9 // Check if the count of zeros is odd (zeros can only pair with themselves)
10 // If odd, we cannot form valid pairs
11 if ((frequencyMap.getOrDefault(0, 0) & 1) != 0) {
12 return false;
13 }
14
15 // Sort all unique numbers by their absolute values
16 // This ensures we process smaller absolute values first
17 List<Integer> sortedKeys = new ArrayList<>(frequencyMap.keySet());
18 sortedKeys.sort(Comparator.comparingInt(Math::abs));
19
20 // Process each number in order of increasing absolute value
21 for (int currentNumber : sortedKeys) {
22 int currentFrequency = frequencyMap.get(currentNumber);
23 int doubleValue = currentNumber << 1; // Equivalent to currentNumber * 2
24
25 // Check if there are enough double values to pair with current number
26 if (frequencyMap.getOrDefault(doubleValue, 0) < currentFrequency) {
27 return false;
28 }
29
30 // Reduce the count of double values by the number of pairs formed
31 frequencyMap.put(doubleValue,
32 frequencyMap.getOrDefault(doubleValue, 0) - currentFrequency);
33 }
34
35 return true;
36 }
37}
38
1class Solution {
2public:
3 bool canReorderDoubled(vector<int>& arr) {
4 // Count frequency of each number in the array
5 unordered_map<int, int> frequencyMap;
6 for (int& value : arr) {
7 ++frequencyMap[value];
8 }
9
10 // Special case: if we have odd number of zeros, we can't pair them
11 if (frequencyMap[0] & 1) {
12 return false;
13 }
14
15 // Extract all unique numbers from the frequency map
16 vector<int> uniqueNumbers;
17 for (auto& [number, count] : frequencyMap) {
18 uniqueNumbers.push_back(number);
19 }
20
21 // Sort numbers by their absolute value (smallest absolute value first)
22 // This ensures we process numbers in order where we can pair x with 2x
23 sort(uniqueNumbers.begin(), uniqueNumbers.end(),
24 [](int a, int b) { return abs(a) < abs(b); });
25
26 // For each number, try to pair it with its double
27 for (int& number : uniqueNumbers) {
28 int doubleValue = number * 2;
29
30 // Check if we have enough doubles to pair with current number
31 if (frequencyMap[doubleValue] < frequencyMap[number]) {
32 return false;
33 }
34
35 // Use up the doubles by pairing them with current number
36 frequencyMap[doubleValue] -= frequencyMap[number];
37 }
38
39 return true;
40 }
41};
42
1function canReorderDoubled(arr: number[]): boolean {
2 // Count frequency of each number in the array
3 const frequencyMap = new Map<number, number>();
4 for (const value of arr) {
5 frequencyMap.set(value, (frequencyMap.get(value) || 0) + 1);
6 }
7
8 // Special case: if we have odd number of zeros, we can't pair them
9 const zeroCount = frequencyMap.get(0) || 0;
10 if (zeroCount & 1) {
11 return false;
12 }
13
14 // Extract all unique numbers from the frequency map
15 const uniqueNumbers: number[] = Array.from(frequencyMap.keys());
16
17 // Sort numbers by their absolute value (smallest absolute value first)
18 // This ensures we process numbers in order where we can pair x with 2x
19 uniqueNumbers.sort((a, b) => Math.abs(a) - Math.abs(b));
20
21 // For each number, try to pair it with its double
22 for (const number of uniqueNumbers) {
23 const doubleValue = number * 2;
24 const currentFrequency = frequencyMap.get(number) || 0;
25 const doubleFrequency = frequencyMap.get(doubleValue) || 0;
26
27 // Check if we have enough doubles to pair with current number
28 if (doubleFrequency < currentFrequency) {
29 return false;
30 }
31
32 // Use up the doubles by pairing them with current number
33 frequencyMap.set(doubleValue, doubleFrequency - currentFrequency);
34 }
35
36 return true;
37}
38
Time and Space Complexity
Time Complexity: O(n log n)
The time complexity is dominated by the sorting operation:
- Creating the frequency counter takes
O(n)
wheren
is the length of the input array - Sorting the frequency keys by absolute value takes
O(k log k)
wherek
is the number of unique elements. In the worst case,k = n
, so this becomesO(n log n)
- The iteration through sorted keys takes
O(k)
time, with each operation inside beingO(1)
- Overall time complexity:
O(n) + O(n log n) + O(n) = O(n log n)
Space Complexity: O(n)
The space complexity comes from:
- The frequency counter
freq
stores at mostn
unique elements (when all elements are distinct), requiringO(n)
space - The sorted operation creates a new list of keys, which takes
O(k)
space wherek โค n
- Overall space complexity:
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not handling zeros correctly
A frequent mistake is forgetting that zeros need special handling. Since 0 * 2 = 0
, zeros can only pair with themselves, requiring an even count.
Incorrect approach:
# Missing zero check
for x in sorted(freq, key=abs):
if freq[x * 2] < freq[x]:
return False
freq[x * 2] -= freq[x]
Correct approach:
if freq[0] % 2 == 1: # Check zeros first return False
2. Processing order confusion with negative numbers
The algorithm must process numbers by absolute value to ensure correct pairing. Processing in regular sorted order fails for negative numbers.
Incorrect approach:
# Wrong: sorts by value, not absolute value
for x in sorted(freq):
# This processes -4 before -2, causing incorrect pairing
Correct approach:
for x in sorted(freq, key=abs): # Sort by absolute value
# This processes -2 before -4, enabling correct pairing
3. Double counting when a number appears multiple times
Some implementations try to check if 2*x
exists without considering frequencies, leading to incorrect results when elements repeat.
Incorrect approach:
# Wrong: only checks existence, not frequency for x in arr: if 2*x not in arr: return False
Correct approach:
# Must track and update frequencies if freq[x * 2] < freq[x]: return False freq[x * 2] -= freq[x] # Decrease available doubles
4. Not considering that each element must be used exactly once
A pitfall is thinking we just need to find pairs without ensuring all elements are used exactly once in the pairing.
Example scenario:
For arr = [1, 2, 4, 8]
:
- We could pair
(1,2)
and(4,8)
- But we could also try pairing
(2,4)
, leaving 1 and 8 unpaired - The algorithm must systematically ensure all elements get paired
The greedy approach starting from smallest absolute values prevents this issue by deterministically choosing pairs.
Which data structure is used in a depth first search?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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!