Difficulty: Easy Pattern: Two Pointers LeetCode: https://leetcode.com/problems/squares-of-a-sorted-array/
Given an integer array nums sorted in non-decreasing order, return an
array of the squares of each element, also sorted in non-decreasing order. The
input may contain negative numbers, and the result must be produced in O(n)
time.
Signature:
int[] sortedSquares(int[] nums)
Examples (verbatim from LeetCode):
Input: nums = [-4,-1,0,3,10]
Output: [0,1,9,16,100]
Input: nums = [-7,-3,2,3,11]
Output: [4,9,9,49,121]
Picture feeding a sorted list through a “squaring machine”: every number becomes
its square. That sounds like it should stay sorted – but negatives flip to
positive. The list [-4, -1, 0, 3, 10] becomes [16, 1, 0, 9, 100], which is
not sorted, because a big negative like -4 turned into a big positive 16.
So squaring each element and leaving it in place does not work, and squaring then
re-sorting works but costs O(n log n) when we are asked for O(n).
The key shape: after squaring, the biggest values sit at the two ends and the smallest (zero) sits in the middle – like a valley. That is exactly the shape that two pointers at opposite ends handles. Put one finger on the left end and one on the right; at each step compare the two squares, take the larger, and move that finger inward.
Let’s watch the tiniest case, nums = [-2, 0, 1]:
left=0 (-2, square 4), right=2 (1, square 1): 4 is bigger, so 4
is the biggest overall. Write it into the last slot of the result, move left
inward.left=1 (0, square 0), right=2 (1, square 1): 1 is bigger, write
1 into the second-to-last slot, move right inward.left=1, right=1 (both on 0, square 0): the fingers have met, write 0
into the first slot. Result: [0, 1, 4].Why write into the result from the back? Because we always pull the larger
square first, so the natural output order is biggest-to-smallest (descending).
Filling the result array from its last index backward flips that into
smallest-to-biggest – exactly the sorted
ascending order we need – in a single pass. We use left <= right (not <) so
that when the two fingers meet on one middle element, its square still gets
written rather than dropped.
Pause and pick before expanding. A wrong first guess teaches more than a fast right one.
Q1 (recall). Why is the result array filled from the LAST index toward the first?
Q2 (comprehend). Why does the loop use left <= right instead of left < right?
< would skip it and leave a gapfunction sortedSquares(nums):
n = length of nums
result = new array of length n
set left to first index
set right to last index
set position to last index of result # fill from the back
while left <= right:
leftSquare = nums[left] squared
rightSquare = nums[right] squared
if leftSquare is greater than rightSquare:
put leftSquare into result[position]
advance left
else:
put rightSquare into result[position]
move right back by one
move position back by one
return result
class Solution {
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int[] result = new int[n];
int left = 0, right = n - 1, pos = n - 1;
while (left <= right) {
int leftSquare = nums[left] * nums[left];
int rightSquare = nums[right] * nums[right];
if (leftSquare > rightSquare) {
result[pos--] = leftSquare;
left++;
} else {
result[pos--] = rightSquare;
right--;
}
}
return result;
}
}
We allocate a fresh result array of the same length and fill it from index
n-1 down to 0, placing the larger square first so the array ends up in
ascending order. The loop condition is left <= right (note the <=) rather
than <: when the two pointers meet on a single middle element, that element’s
square still needs a home, so we must process it. The constraint
|nums[i]| <= 10^4 guarantees the square fits in int (max 10^8), so no
long is needed.
Time: O(n) -- each iteration writes one cell and moves one pointer; exactly n iterations.
Space: O(n) -- the output array. If we do not count the output (LeetCode convention), O(1) extra.
Step-by-step on nums = [-4,-1,0,3,10] (n = 5).
| Step | left | right | nums[left]^2 | nums[right]^2 | bigger | result so far (index:value) | move |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 4 | 16 | 100 | 100 | [4]=100 | right– |
| 2 | 0 | 3 | 16 | 9 | 16 | [3]=16 | left++ |
| 3 | 1 | 3 | 1 | 9 | 9 | [2]=9 | right– |
| 4 | 1 | 2 | 1 | 0 | 1 | [1]=1 | left++ |
| 5 | 2 | 2 | 0 | 0 | 0 | [0]=0 | left++ (left>right, exit) |
Final result = [0,1,9,16,100]. The middle element 0 at index 2 is processed
at step 5 because of the <= condition — that is the whole reason for the
inclusive bound.
Q1 (apply). Trace nums = [-3,-1,2]. What is the output array?
[1,4,9][9,4,1][4,9,1]Q2 (analyze). What goes wrong if you write while (left < right) (no equals)?
result is left at its default 0 and a real value is droppedQ3 (transfer). Suppose the O(n) requirement still held, but you were FORBIDDEN from allocating a new output array (a truly in-place result). How would you reason about it?
left < right. This skips the middle element when the pointers meet,
leaving one cell of result as 0 by accident and dropping a real value.-7 > 3 as integers but 49 < 9 is
false; always compare the squared values, not the original signs.