This code looks a lot like a C programmer's attempt to write Python. It's full of hand-built loops indexing the list, and doesn't make any use of Python's rich standard library.
The two functions that could replace most of the code we have are bisect.bisect() and heapq.merge(); another Python feature that helps is making slice viewsslices of parts of the input list.
Here's how I'd use those functions to write an alternative:
from bisect import bisect
from heapq import merge
def square(n):
return n*n
def sorted_squares(nums):
z = bisect(nums, 0) # zero-crossing point
return merge(map(square, reversed(nums[:z])),
map(square, nums[z:]))
See how much easier this is to reason about, without having to hold the intricacies of control flow in your head as you read? Just three lines of list manipulation replaces 45 lines in the original, and we've reduced the number of variables from at least 6 down to just one. And instead of four levels of indentation, everything is now at a single level. All that goes together to make something that's much more comprehensible.
If memory use is vitally important, we might use itertools.islice() to avoid copying list contents (at the expense of reducing clarity somewhat):
neg = islice(reversed(nums), len(nums)-z, None)
pos = islice(nums, z, None)
return merge(map(square, neg), map(square, pos))
Quick demo:
if __name__ == '__main__':
print(list(sorted_squares([-4,-1,0,3,10])))
That gives the expected result:
[0, 1, 9, 16, 100]
And converting to list for the stated requirement:
class Solution:
def sortedSquares(self, nums: list[int]) -> list[int]:
return list(sorted_squares(nums))
I believe it's worth separating the conversion like that, if the function might be reused somewhere that constructing the whole list in memory is unnecessary.