Facebook Pixel

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 linear f(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]
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. 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.

  2. 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.

  3. For a = 0: The function becomes linear (f(x) = bx + c), which preserves the relative ordering if b > 0 or reverses it if b < 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) and j = 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

Step 3: Process elements using two pointers While i <= j:

  1. Calculate transformed values: v1 = f(nums[i]) and v2 = f(nums[j])

  2. When a < 0 (downward parabola):

    • The minimum values are at the extremes
    • Compare v1 and v2, take the smaller one
    • Place it at position k and increment k (building array from start)
    • Move the corresponding pointer inward
  3. When a >= 0 (upward parabola or linear):

    • The maximum values are at the extremes
    • Compare v1 and v2, take the larger one
    • Place it at position k and decrement k (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 (since a > 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 Evaluator

Example 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 position k=0
  • Move left pointer: i = 1
  • Result: [-14, _, _, _], increment k = 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 position k=1
  • Move left pointer: i = 2
  • Result: [-14, -2, _, _], increment k = 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 position k=2
  • Move right pointer: j = 2
  • Result: [-14, -2, -2, _], increment k = 3

Iteration 4:

  • Only one element left: i = j = 2
  • f(2) = 1
  • Place 1 at position k=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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How many ways can you arrange the three letters A, B and C?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More