360. Sort Transformed Array π
Problem Description
You are given a sorted integer array nums
and three integer coefficients a
, b
, and c
. Your task is to apply a quadratic function f(x) = axΒ² + bx + c
to each element in the array and return the resulting values in sorted order.
The challenge lies in efficiently sorting the transformed values. Since the input array is already sorted, and we're applying a quadratic function, the solution leverages the properties of parabolas:
- When
a > 0
, the parabola opens upward (U-shaped), meaning the maximum values are at the ends of the sorted input array - When
a < 0
, the parabola opens downward (β©-shaped), meaning the minimum values are at the ends of the sorted input array - When
a = 0
, the function becomes linearf(x) = bx + c
The solution uses a two-pointer approach, starting from both ends of the input array. It compares the transformed values at both pointers and fills the result array:
- If
a >= 0
, we fill from the end of the result array backwards (since larger values are at the extremes) - If
a < 0
, we fill from the beginning of the result array forwards (since smaller values are at the extremes)
For example, if nums = [-4, -2, 2, 4]
, a = 1
, b = 3
, c = 5
:
- Transform each element:
f(-4) = 9
,f(-2) = 3
,f(2) = 15
,f(4) = 33
- Return the sorted result:
[3, 9, 15, 33]
Intuition
The key insight comes from understanding the shape of quadratic functions. When we apply f(x) = axΒ² + bx + c
to a sorted array, we need to recognize that a quadratic function forms a parabola.
Let's think about what happens to our sorted values after transformation:
-
For positive
a
(upward parabola): The function has a minimum point (vertex). Since our input is sorted, the transformed values will be highest at the extremes (leftmost and rightmost elements) and lowest somewhere in the middle. Imagine a U-shape - the further from the vertex, the larger the value. -
For negative
a
(downward parabola): The function has a maximum point (vertex). The transformed values will be lowest at the extremes and highest somewhere in the middle. Picture an inverted U-shape. -
For
a = 0
: The function becomes linear (f(x) = bx + c
), which preserves the relative ordering ifb > 0
or reverses it ifb < 0
.
Since we know the extreme values (either maximum or minimum) are at the ends of our sorted input array, we can use a two-pointer technique. We don't need to transform all values and then sort them - that would take O(n log n)
time. Instead, we can build our sorted result in O(n)
time by:
- Comparing transformed values from both ends
- Taking the larger one if we're building from largest to smallest (
a >= 0
) - Taking the smaller one if we're building from smallest to largest (
a < 0
) - Moving the corresponding pointer inward
This works because once we've taken an extreme value, the next extreme value must come from either the same end (moving one step inward) or the opposite end. We're essentially "peeling off" values from the outside in, maintaining sorted order based on the parabola's properties.
Solution Approach
The implementation uses a two-pointer technique combined with understanding of parabola properties:
Step 1: Define the transformation function
def f(x):
return a * x * x + b * x + c
Step 2: Initialize pointers and result array
- Set two pointers:
i = 0
(left) andj = n - 1
(right) - Create result array of size
n
- Initialize position
k
where we'll place values:- If
a < 0
: Start from beginning (k = 0
) since we'll place smaller values first - If
a >= 0
: Start from end (k = n - 1
) since we'll place larger values first
- If
Step 3: Process elements using two pointers
While i <= j
:
-
Calculate transformed values:
v1 = f(nums[i])
andv2 = f(nums[j])
-
When
a < 0
(downward parabola):- The minimum values are at the extremes
- Compare
v1
andv2
, take the smaller one - Place it at position
k
and incrementk
(building array from start) - Move the corresponding pointer inward
-
When
a >= 0
(upward parabola or linear):- The maximum values are at the extremes
- Compare
v1
andv2
, take the larger one - Place it at position
k
and decrementk
(building array from end) - Move the corresponding pointer inward
Algorithm walkthrough with example:
- Input:
nums = [-2, 0, 2]
,a = 1
,b = 0
,c = 0
(function:f(x) = xΒ²
) - Initial:
i = 0
,j = 2
,k = 2
(sincea > 0
) - Iteration 1:
f(-2) = 4
,f(2) = 4
, take either (both equal), place 4 at index 2 - Iteration 2: Compare remaining values, place 4 at index 1
- Iteration 3:
f(0) = 0
, place 0 at index 0 - Result:
[0, 4, 4]
Time Complexity: O(n)
- single pass through the array
Space Complexity: O(n)
- for the result array (or O(1)
if we don't count the output)
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 illustrate the solution approach.
Input: nums = [-3, -1, 2, 3]
, a = -1
, b = 2
, c = 1
Function: f(x) = -xΒ² + 2x + 1
Since a = -1 < 0
, we have a downward parabola (β©-shaped), meaning minimum values are at the extremes.
Step 1: Initialize
- Two pointers:
i = 0
(pointing to -3),j = 3
(pointing to 3) - Result array:
[_, _, _, _]
- Since
a < 0
, we fill from the beginning:k = 0
Step 2: Transform and compare extremes
Iteration 1:
- Left value:
f(-3) = -(-3)Β² + 2(-3) + 1 = -9 - 6 + 1 = -14
- Right value:
f(3) = -(3)Β² + 2(3) + 1 = -9 + 6 + 1 = -2
- Since
-14 < -2
, place-14
at positionk=0
- Move left pointer:
i = 1
- Result:
[-14, _, _, _]
, incrementk = 1
Iteration 2:
- Left value:
f(-1) = -(-1)Β² + 2(-1) + 1 = -1 - 2 + 1 = -2
- Right value:
f(3) = -2
(already calculated) - Both equal, take either (let's take left)
- Place
-2
at positionk=1
- Move left pointer:
i = 2
- Result:
[-14, -2, _, _]
, incrementk = 2
Iteration 3:
- Left value:
f(2) = -(2)Β² + 2(2) + 1 = -4 + 4 + 1 = 1
- Right value:
f(3) = -2
- Since
-2 < 1
, place-2
at positionk=2
- Move right pointer:
j = 2
- Result:
[-14, -2, -2, _]
, incrementk = 3
Iteration 4:
- Only one element left:
i = j = 2
f(2) = 1
- Place
1
at positionk=3
- Result:
[-14, -2, -2, 1]
Final Output: [-14, -2, -2, 1]
(sorted in ascending order)
The algorithm efficiently produces the sorted result in O(n) time by leveraging the parabola's property that extreme values (minimums for downward parabola) are at the ends of the sorted input array.
Solution Implementation
1class Solution:
2 def sortTransformedArray(
3 self, nums: List[int], a: int, b: int, c: int
4 ) -> List[int]:
5 """
6 Apply quadratic transformation f(x) = ax^2 + bx + c to sorted array
7 and return the result in sorted order.
8
9 Key insight: Parabola properties
10 - When a > 0: parabola opens upward (U-shape), extremes are larger
11 - When a < 0: parabola opens downward (β©-shape), extremes are smaller
12 - When a = 0: linear function, maintains monotonic property
13 """
14
15 def quadratic_transform(x: int) -> int:
16 """Calculate the quadratic transformation of x."""
17 return a * x * x + b * x + c
18
19 n = len(nums)
20 result = [0] * n
21
22 # Two pointers approach from both ends
23 left = 0
24 right = n - 1
25
26 # Determine filling direction based on parabola shape
27 # If a < 0 (downward parabola): fill from start (smaller values first)
28 # If a >= 0 (upward parabola or line): fill from end (larger values first)
29 index = 0 if a < 0 else n - 1
30
31 while left <= right:
32 # Calculate transformed values at both ends
33 left_value = quadratic_transform(nums[left])
34 right_value = quadratic_transform(nums[right])
35
36 if a < 0:
37 # Downward parabola: pick smaller value and fill from start
38 if left_value <= right_value:
39 result[index] = left_value
40 left += 1
41 else:
42 result[index] = right_value
43 right -= 1
44 index += 1
45 else:
46 # Upward parabola or line: pick larger value and fill from end
47 if left_value >= right_value:
48 result[index] = left_value
49 left += 1
50 else:
51 result[index] = right_value
52 right -= 1
53 index -= 1
54
55 return result
56
1class Solution {
2 /**
3 * Sorts the transformed array after applying quadratic function f(x) = axΒ² + bx + c
4 * Uses two-pointer technique leveraging the parabolic properties of quadratic functions
5 *
6 * @param nums sorted input array
7 * @param a coefficient of xΒ²
8 * @param b coefficient of x
9 * @param c constant term
10 * @return sorted transformed array
11 */
12 public int[] sortTransformedArray(int[] nums, int a, int b, int c) {
13 int n = nums.length;
14 int[] result = new int[n];
15
16 // Two pointers: left starts from beginning, right from end
17 int left = 0;
18 int right = n - 1;
19
20 // When a < 0, parabola opens downward - smallest values at extremes
21 // When a >= 0, parabola opens upward - largest values at extremes
22 // Fill result array accordingly: from start if a < 0, from end if a >= 0
23 int index = (a < 0) ? 0 : n - 1;
24
25 // Process elements using two pointers
26 while (left <= right) {
27 // Calculate transformed values for both ends
28 int leftValue = calculateQuadratic(a, b, c, nums[left]);
29 int rightValue = calculateQuadratic(a, b, c, nums[right]);
30
31 if (a < 0) {
32 // Parabola opens downward: pick smaller value first
33 if (leftValue <= rightValue) {
34 result[index] = leftValue;
35 left++;
36 } else {
37 result[index] = rightValue;
38 right--;
39 }
40 index++; // Move forward in result array
41 } else {
42 // Parabola opens upward or is linear: pick larger value first
43 if (leftValue >= rightValue) {
44 result[index] = leftValue;
45 left++;
46 } else {
47 result[index] = rightValue;
48 right--;
49 }
50 index--; // Move backward in result array
51 }
52 }
53
54 return result;
55 }
56
57 /**
58 * Calculates the quadratic function value: f(x) = axΒ² + bx + c
59 *
60 * @param a coefficient of xΒ²
61 * @param b coefficient of x
62 * @param c constant term
63 * @param x input value
64 * @return transformed value
65 */
66 private int calculateQuadratic(int a, int b, int c, int x) {
67 return a * x * x + b * x + c;
68 }
69}
70
1class Solution {
2public:
3 vector<int> sortTransformedArray(vector<int>& nums, int a, int b, int c) {
4 int arraySize = nums.size();
5 vector<int> result(arraySize);
6
7 // Two pointers approach: start from both ends of the sorted input array
8 int leftIndex = 0;
9 int rightIndex = arraySize - 1;
10
11 // Determine where to start filling the result array:
12 // - If a < 0: parabola opens downward, smallest values are at the ends
13 // so we fill result from beginning (index 0)
14 // - If a >= 0: parabola opens upward, largest values are at the ends
15 // so we fill result from end (index n-1)
16 int resultIndex = (a < 0) ? 0 : arraySize - 1;
17
18 // Process elements from both ends simultaneously
19 while (leftIndex <= rightIndex) {
20 // Calculate transformed values for both ends
21 int leftValue = calculateQuadratic(a, b, c, nums[leftIndex]);
22 int rightValue = calculateQuadratic(a, b, c, nums[rightIndex]);
23
24 if (a < 0) {
25 // Parabola opens downward: pick smaller value first
26 if (leftValue <= rightValue) {
27 result[resultIndex] = leftValue;
28 leftIndex++;
29 } else {
30 result[resultIndex] = rightValue;
31 rightIndex--;
32 }
33 resultIndex++; // Move forward in result array
34 } else {
35 // Parabola opens upward or is linear: pick larger value first
36 if (leftValue >= rightValue) {
37 result[resultIndex] = leftValue;
38 leftIndex++;
39 } else {
40 result[resultIndex] = rightValue;
41 rightIndex--;
42 }
43 resultIndex--; // Move backward in result array
44 }
45 }
46
47 return result;
48 }
49
50private:
51 // Helper function to calculate the quadratic transformation
52 // f(x) = ax^2 + bx + c
53 int calculateQuadratic(int a, int b, int c, int x) {
54 return a * x * x + b * x + c;
55 }
56};
57
1function sortTransformedArray(nums: number[], a: number, b: number, c: number): number[] {
2 const arraySize = nums.length;
3 const result: number[] = new Array(arraySize);
4
5 // Two pointers approach: start from both ends of the sorted input array
6 let leftIndex = 0;
7 let rightIndex = arraySize - 1;
8
9 // Determine where to start filling the result array:
10 // - If a < 0: parabola opens downward, smallest values are at the ends
11 // so we fill result from beginning (index 0)
12 // - If a >= 0: parabola opens upward, largest values are at the ends
13 // so we fill result from end (index n-1)
14 let resultIndex = a < 0 ? 0 : arraySize - 1;
15
16 // Process elements from both ends simultaneously
17 while (leftIndex <= rightIndex) {
18 // Calculate transformed values for both ends
19 const leftValue = calculateQuadratic(a, b, c, nums[leftIndex]);
20 const rightValue = calculateQuadratic(a, b, c, nums[rightIndex]);
21
22 if (a < 0) {
23 // Parabola opens downward: pick smaller value first
24 if (leftValue <= rightValue) {
25 result[resultIndex] = leftValue;
26 leftIndex++;
27 } else {
28 result[resultIndex] = rightValue;
29 rightIndex--;
30 }
31 resultIndex++; // Move forward in result array
32 } else {
33 // Parabola opens upward or is linear: pick larger value first
34 if (leftValue >= rightValue) {
35 result[resultIndex] = leftValue;
36 leftIndex++;
37 } else {
38 result[resultIndex] = rightValue;
39 rightIndex--;
40 }
41 resultIndex--; // Move backward in result array
42 }
43 }
44
45 return result;
46}
47
48// Helper function to calculate the quadratic transformation
49// f(x) = ax^2 + bx + c
50function calculateQuadratic(a: number, b: number, c: number, x: number): number {
51 return a * x * x + b * x + c;
52}
53
Time and Space Complexity
Time Complexity: O(n)
The algorithm uses a two-pointer approach with pointers i
and j
starting from opposite ends of the input array. In each iteration of the while loop, exactly one pointer moves (either i
increments or j
decrements). The loop continues while i <= j
, meaning each element in the array is visited exactly once. The function f(x)
performs a constant time calculation (O(1)
). Therefore, the overall time complexity is O(n)
, where n
is the length of the input array.
Space Complexity: O(n)
The algorithm creates a result array res
of size n
to store the transformed and sorted values. This is the only additional space used that scales with the input size. The other variables (i
, j
, k
, v1
, v2
) use constant space O(1)
. Since we need to return a new sorted array and cannot sort in-place due to the transformation, the O(n)
space for the result array is necessary. Therefore, the total space complexity is O(n)
.
Common Pitfalls
1. Incorrect Comparison Logic for Different Parabola Orientations
One of the most common mistakes is mixing up the comparison logic when determining which value to place in the result array. Developers often confuse when to pick the smaller vs. larger value based on the parabola's orientation.
Pitfall Example:
# INCORRECT: Using the same comparison logic for both cases if left_value <= right_value: result[index] = left_value left += 1 else: result[index] = right_value right -= 1 # This doesn't account for different parabola orientations!
Why This Happens:
- When
a < 0
(downward parabola), we want to build the sorted array from smallest to largest, so we pick the smaller value from the extremes - When
a >= 0
(upward parabola), we want to build from largest to smallest (filling backwards), so we pick the larger value from the extremes
Solution: Always use opposite comparison logic for the two cases:
if a < 0: # Pick SMALLER value for ascending order if left_value <= right_value: result[index] = left_value left += 1 else: result[index] = right_value right -= 1 else: # Pick LARGER value for descending fill (then reversed) if left_value >= right_value: result[index] = left_value left += 1 else: result[index] = right_value right -= 1
2. Forgetting to Handle the Linear Case (a = 0)
Another pitfall is not properly handling when a = 0
, which makes the function linear rather than quadratic.
Pitfall Example:
# INCORRECT: Only checking a > 0 and a < 0 if a > 0: index = n - 1 elif a < 0: index = 0 # Missing: What if a == 0?
Why This Matters:
When a = 0
, the function becomes f(x) = bx + c
. The transformation maintains the monotonic property of the sorted input:
- If
b >= 0
, the array remains in ascending order - If
b < 0
, the array becomes descending
Solution:
Group a = 0
with a > 0
case (or handle it separately based on b
):
# Correct approach: a >= 0 handles both upward parabola and linear cases index = 0 if a < 0 else n - 1
3. Index Management Errors
Incorrectly updating the result array index can lead to overwriting values or accessing out-of-bounds indices.
Pitfall Example:
# INCORRECT: Forgetting to update index or updating in wrong direction while left <= right: # ... comparison logic ... result[index] = selected_value # Forgot to update index! # Or updating index++ when should be index--
Solution: Ensure index updates match the filling direction:
if a < 0: # Fill forward: increment index result[index] = selected_value index += 1 else: # Fill backward: decrement index result[index] = selected_value index -= 1
4. Not Recognizing the Two-Pointer Opportunity
A naive approach would be to transform all values first, then sort them, resulting in O(n log n) complexity.
Pitfall Example:
# INEFFICIENT: Transform then sort
def sortTransformedArray(nums, a, b, c):
result = []
for x in nums:
result.append(a * x * x + b * x + c)
return sorted(result) # O(n log n) - unnecessary!
Solution: Leverage the parabola properties and sorted input to achieve O(n) complexity using the two-pointer technique as shown in the main solution.
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Want a Structured Path to Master System Design Too? Donβt Miss This!