modern annotations
These are lovely, thank you.
def sortedSquares(self, nums: List[int]) -> List[int]:
They're very clear. But for modern cPython interpreters, 3.9+, prefer list[int]
without the from typing import List
.
Also, PEP 8 asks that you spell it sorted_squares()
(unless LeetCode imposes some weird non-pythonic requirement on folks offering python submissions).
comments lie!
They lie all too often. People don't mean for it to happen; it just does.
# binary search O(log n) if nums[0] == 0 or nums[0]> 0: for i in range(n): nums[i] = nums[i]**2 return nums elif nums[n-1] == 0 or nums[n-1] < 0: nums.reverse() for i in range(n): nums[i] = nums[i]**2 return nums
This in no way resembles a binary search. The relevant remark would explain that we're seeing if we're in the special case of "all positive" or "all negative".
>=
if nums[0] == 0 or nums[0]> 0:
That's an odd way to phrase if nums[0] >= 0:
elif nums[n-1] ...
That's perfectly fine, but the idiomatic way to talk about the last element would be nums[-1]
.
In any event, you accomplished the special casing in \$O(n)\$ linear time, so no harm done. Mostly we're trying to avoid the extra \$\log n\$ factor that sorting would introduce.
meaningful identifiers
k1 = 0 k = n-1
The i
and j
indexes were great, but these two are terrible terrible identifiers. Pick names that show what they mean, or at least offer a # comment
to help out the Gentle Reader. The and k!=k1
conjunct suggests that you have some loop invariant in mind, but you didn't go to the trouble of telling us what it is, how it relates to the business problem at hand.
The k = (i+j)//2
expression suggests that mid
or midpoint
might be a relevant name to use.
q = k+1
I don't know what this means.
If you were hoping to communicate an idea to the machine, good, chalk up a win. If you were hoping to communicate a technical idea to a collaborator, this attempt was not as effective as it could have been. Use English narrative, or descriptive names, or at least the crutch of # comments
.
extract helper
if nums[k] == 0: break elif nums[k] > 0: j = k
Why are these even special cases?
The real thing you wish to do is find a sign change. Recall that \$O(n + \log n) = O(n)\$. So, bonus points for the binary search, I guess? But a simple linear search for sign change would have remained within your \$O(n)\$ budget.
The if k < (n-1)//2:
loop offers another fruitful opportunity to Extract Helper. Minimally you get to def foo()
where "foo" usefully describes what's happening. Ideally you would add a """docstring""" that offers further details.
Recall that the bisect module stands at the ready to help with many of your binary search needs.
algorithm
There's only two cases here. A number is negative or it isn't. I would expect a solution to follow 2-way merge:
- Linear scan to find change-of-sign (if any).
- Maintain indices
i
and j
which go outward from zero. While absolute value of one or the other is "winning", emit its square. Then keep iterating.
Both steps have constant memory cost and linear time complexity.
Given that we already know the size of the output array, we don't even need to scan for where the sign change happens, since we can fill it in in reverse. Simply init i
& j
to start and end, and do 2-way merge inward, emitting squares as you go, in reverse order. At some point the two indexes will locate where the sign change is, and then we're done.
constant time solution
Notice the constraints.
There are at most 10_000
elements, and their absolute values shall be at most 10_000
. It's not hard to allocate a container of that size. Simply store absolute values of the inputs, and then read them out in sequence. No \$\log n\$ term involved at all. Is this cheating? Yes. Kind of. The key is to figure out the rules, and play within them. (And benchmark against the previous approach. Which this won't be competitive with, for most input sizes.)