Squares of a Sorted Array

Problem

Given an integer array nums sorted in non-decreasing order, return an array of the squares of each number sorted in non-decreasing order.

  • 1 ≤ nums.length ≤ 10⁴
  • −10⁴ ≤ nums[i] ≤ 10⁴
  • nums is sorted in non-decreasing order

Example

Input: nums = [-7,-3,2,3,11]
Output: [4,9,9,49,121]

The brute-force approach squares every element and then sorts the array, which takes O(n log n) time and ignores that nums is already sorted. The optimal method uses two pointers at the ends of the array and fills the result from the back: - Start: l = 0 (value -7, square 49), r = 4 (value 11, square 121). Place 121 at ans[4], move r to 3. - Next: l = 0 (square 49), r = 3 (value 3, square 9). Place 49 at ans[3], move l to 1. - Next: l = 1 (value -3, square 9), r = 3 (value 3, square 9). Squares tie, place 9 at ans[2], move r to 2. - Continue this process, placing the larger square at ans[i] and moving the corresponding pointer inward, until l > r. By always writing the largest remaining square at the current back position, the output array becomes [4, 9, 9, 49, 121] in O(n) time without an extra sort.

Approach

Straightforward Solution

Square all elements and sort the resulting array. This approach takes O(n log n) time due to sorting and does not leverage the input's sorted property.

Core Observation

The squares of a sorted array are not necessarily sorted because negative numbers become positive. However, the largest squared values must come from either end of the array since the array is sorted.

Path to Optimal

Recognizing that the largest squares come from the array's ends, a two-pointer approach can be used. By comparing the squares at the left and right pointers, the algorithm places the larger square at the current position from the end of the output array and moves the corresponding pointer inward. This process continues until the pointers cross, resulting in a sorted squares array in O(n) time.

Optimal Approach

Initialize two pointers at the start and end of the input array. Create an output array of the same size. Iterate from the end of the output array backward, at each step comparing the squares of the elements at the two pointers. Place the larger square at the current output position and move the corresponding pointer inward. Continue until all positions are filled, achieving O(n) time and O(n) space.

Time

O(n)

Each element is visited exactly once by either the left or right pointer, and each comparison and assignment is O(1), resulting in a linear time complexity.

Space

O(n)

An output array of size n is created to store the sorted squares. This auxiliary space is necessary since the output must be returned separately from the input.

Pattern Spotlight

Two Pointers (Greedy Contraction)

The largest squared values must come from the ends of the sorted array, so by comparing and placing the larger square at the end of the output array and moving pointers inward, the algorithm efficiently constructs the sorted squares in a single pass.

Solution

1class Solution:
2 def sortedSquares(self, nums: List[int]) -> List[int]:
3 n = len(nums)
4 ans = [0] * n
5
6 l, r = 0, n - 1
7 i = n - 1
8
9 while l <= r:
10 left_sq = nums[l] * nums[l]
11 right_sq = nums[r] * nums[r]
12
13 if left_sq > right_sq:
14 ans[i] = left_sq
15 l += 1
16 else:
17 ans[i] = right_sq
18 r -= 1
19
20 i -= 1
21
22 return ans

Step-by-Step Solution

1

Initialize Pointers and Output Array

3n = len(nums)
4ans = [0] * n
6l, r = 0, n - 1
7i = n - 1

Objective

To set up the two pointers at the start and end of the input array and prepare the output array to store results.

Key Insight

Starting pointers at both ends leverages the sorted property of the input array. The output array is preallocated to avoid dynamic resizing and to allow filling from the end, which matches the order of largest squares found.

Interview Quick-Check

Core Logic

Two pointers at the array boundaries allow simultaneous comparison of the largest magnitude elements, which after squaring, produce the largest values.

State & Boundaries

The output array is indexed from the end to the start, matching the order in which the largest squares are placed.

Common Pitfalls & Bugs

Forgetting to initialize the output array with the correct size or incorrect pointer positions can lead to index errors.

2

Compare Squares and Fill Output Array from End

9while l <= r:
10 left_sq = nums[l] * nums[l]
11 right_sq = nums[r] * nums[r]
13 if left_sq > right_sq:
14 ans[i] = left_sq
15 l += 1
16 else:
17 ans[i] = right_sq
18 r -= 1
20 i -= 1

Objective

To iteratively compare squares at the left and right pointers, place the larger square at the current output index, and move pointers inward accordingly.

Key Insight

By always placing the larger square at the current output position from the end, the algorithm ensures the output array is sorted without additional sorting. Moving pointers inward discards the processed element, shrinking the search space until all elements are placed.

Interview Quick-Check

Core Logic

At each iteration, compare squares at left and right pointers, place the larger at output[i], then move the pointer corresponding to the larger square inward and decrement i.

State & Boundaries

The loop continues while left pointer is less than or equal to right pointer, ensuring all elements are processed.

Common Pitfalls & Bugs

Incorrect pointer movement or failing to decrement the output index can cause incorrect results or infinite loops.

3

Return the Sorted Squares Array

22return ans

Objective

To return the fully populated output array containing the squares in sorted order.

Key Insight

After the loop completes, the output array is guaranteed to be sorted because the largest squares were placed first, moving backward to the smallest.

Interview Quick-Check

Core Logic

Returning the output array completes the function, providing the sorted squares as required.

Line Analysis

This solution has 1 Critical line interviewers watch for.

Line 13 Critical
if left_sq > right_sq:

Compare the left and right squares to decide which is larger.

This comparison identifies the larger square to place at the current output index, ensuring the output array is sorted.

Line 14 Core
ans[i] = left_sq

Assign the larger left square to the current output index i.

Placing the larger square at the end of the output array maintains sorted order without additional sorting.

Line 17 Core
ans[i] = right_sq

Assign the larger right square to the current output index i.

Placing the larger right square at the output index maintains the sorted order of the output array.

Line 20 Core
i -= 1

Decrement the output index i to fill the output array from end to start.

Decreasing i ensures the next largest square is placed in the correct position, maintaining sorted order.

Line 4 Core
ans = [0] * n

Initialize the output array ans with zeros of length n.

Preallocating the output array allows direct assignment by index, enabling O(1) writes and avoiding dynamic resizing overhead.

Line 7 Core
i = n - 1

Initialize i to the last index of ans, where the next largest square will be written.

The index i starts at the last position of the output array so that each step can place the current largest square and then move left, building the sorted result from largest to smallest.

Line 15 Core
l += 1

Increment the left pointer to move inward after placing its square.

Moving the left pointer inward discards the processed element, shrinking the search space.

Line 16 Core
else:

Else branch for when the right square is greater or equal.

Handles the case where the right square is larger or equal, ensuring correct placement in the output array.

Line 18 Core
r -= 1

Decrement the right pointer to move inward after placing its square.

Moving the right pointer inward discards the processed element, shrinking the search space.

Line 9 Standard
while l <= r:

Begin a loop that continues while the left pointer is less than or equal to the right pointer.

This loop condition ensures all elements between the pointers are processed exactly once, covering the entire array.

Line 10 Standard
left_sq = nums[l] * nums[l]

Calculate the square of the element at the left pointer.

Squaring the left element prepares it for comparison to determine which square is larger.

Line 11 Standard
right_sq = nums[r] * nums[r]

Calculate the square of the element at the right pointer.

Squaring the right element prepares it for comparison to determine which square is larger.

Line 6 Standard
l, r = 0, n - 1

Initialize l and r to the leftmost and rightmost indices of nums.

The two pointers l and r start at the opposite ends of the array so we can always compare the elements with the largest absolute values, which are the only candidates for the next largest square.

Line 22 Standard
return ans

Return the output array containing sorted squares.

Returning the fully populated output array completes the function, providing the required sorted squares.

Line 3 Basic
n = len(nums)

Store the length of the input array nums.

Caching the length avoids repeated calls to len(nums) and clarifies the array size for subsequent operations.

Test Your Understanding

Why does the two-pointer approach fill the output array from the end rather than from the start?

Because the largest squares come from the ends of the input array, placing them at the end of the output array ensures the array is filled in non-decreasing order without needing extra sorting.

Related Problems

Two Pointers pattern

Don't just read it. Drill it.

Reconstruct Squares of a Sorted Array from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Squares of a Sorted Array drill

or drill a free problem