101. Stable In-Place Partition of Positive and Negative Numbers
Given an array containing positive and negative numbers, rearrange it so that all positive numbers appear before all negative numbers while preserving the original relative order within both groups. Perform the rearrangement in place using O(1) auxiliary space, and explain the algorithm, correctness, and time complexity.
I use a stable insertion-and-shift approach. I scan the array from left to right and remember the index of the first negative number. When I later find a positive number, I save it, shift the negative block one position to the right, and insert the positive number at the remembered index. This preserves the original order of both groups. The worst-case time complexity is O(n²), and the auxiliary space complexity is O(1).
See the Code while reading this explanation.
The problem asks us to move all positive numbers before all negative numbers. We must preserve the original order inside both groups. We must also modify the same array and use only constant extra memory. The diagram uses a stable insertion-and-shift method. It remembers the first misplaced negative number and inserts each later positive number before that negative block.
- What input sizes, value ranges, and edge cases should the solution handle?
- What output should be returned for empty, invalid, or duplicate input?
- Should I prioritize execution time or memory use, and may I use the standard library?
The input is an array of positive and negative integers.
For the example:
Input: [3, -1, 2, -2, 5, -3, 4, -4]
Output: [3, 2, 5, 4, -1, -2, -3, -4]
The positive numbers keep their original order: 3, 2, 5, 4.
The negative numbers also keep their original order: -1, -2, -3, -4.
The array must be changed in place. This means we modify the original list instead of building another list.
A normal swap is not enough. Swapping a positive number with an earlier negative number can change the order of the negative numbers.
Instead, we remember the position of the first negative number that must move right. When we find a later positive number, we save that positive number. We then shift the whole negative block one place to the right and insert the positive number at the beginning of that block.
This is similar to inserting an item into an earlier position in an array.
We use one variable named first_negative.
It starts at -1.
A value of -1 means that we have not yet found a negative number that appears before a later positive number.
We then scan the array from left to right using index j.
Start with:
array = [3, -1, 2, -2, 5, -3, 4, -4]
first_negative = -1
At j = 0, the value is 3. It is positive. There is no earlier negative block, so the array does not change.
At j = 1, the value is -1. This is the first negative number, so first_negative becomes 1.
At j = 2, the value is 2. A negative block already starts at index 1. We save 2, shift [-1] one place to the right, and insert 2 at index 1.
The array becomes:
[3, 2, -1, -2, 5, -3, 4, -4]
Then first_negative becomes 2.
At j = 3, the value is -2. It belongs to the negative block, so nothing changes.
At j = 4, the value is 5. We save 5, shift [-1, -2] one place to the right, and insert 5 at index 2.
The array becomes:
[3, 2, 5, -1, -2, -3, 4, -4]
Then first_negative becomes 3.
At j = 5, the value is -3. Nothing changes.
At j = 6, the value is 4. We save 4, shift [-1, -2, -3] one place to the right, and insert 4 at index 3.
The array becomes:
[3, 2, 5, 4, -1, -2, -3, -4]
Then first_negative becomes 4.
At j = 7, the value is -4. Nothing changes.
The traversal is complete.
Before each iteration, every positive number before first_negative is already in the correct relative order.
The negative numbers starting at first_negative also remain in their original relative order.
When we find a later positive number, we do not swap it with only one negative number. We shift the complete negative block one place to the right. This keeps the negative values in the same order.
We then insert the positive value at first_negative. This also keeps the positive values in their original order.
Therefore, both groups remain stable.
The function starts with first_negative = -1.
The outer loop visits each index from left to right.
When nums[j] is negative and first_negative is still -1, the code records j as the beginning of the negative block.
When nums[j] is positive and a negative block already exists, the code saves nums[j] in positive.
The inner loop shifts every value from first_negative through j - 1 one position to the right.
The saved positive value is then written at first_negative.
Finally, first_negative moves one position to the right because the positive region has grown by one element.
The outer loop visits the array once. However, a positive number may require shifting many earlier negative numbers.
In the worst case, many shifts happen for many positive numbers. Therefore, the worst-case time complexity is O(n²).
The algorithm uses only indices and one temporary value. Therefore, the auxiliary space complexity is O(1).
If all values are positive, the array stays unchanged.
If all values are negative, the array stays unchanged.
If the array is already partitioned, no shifts are needed.
An empty array and a one-element array also remain unchanged.
The key idea is to treat the first misplaced negative number as an insertion position. The variable first_negative marks the beginning of the negative block. When a later positive number is found, that value is saved, the negative block between first_negative and the current index is shifted one position to the right, and the positive value is inserted at first_negative. The central invariant is that positives before first_negative stay in their original order, and the encountered negatives from first_negative onward also stay in their original order.
def stable_partition(nums: list[int]) -> list[int]:
first_negative = -1
for j in range(len(nums)):
if nums[j] < 0:
if first_negative == -1:
first_negative = j
elif nums[j] > 0 and first_negative != -1:
positive = nums[j]
for k in range(j, first_negative, -1):
nums[k] = nums[k - 1]
nums[first_negative] = positive
first_negative += 1
return nums
if __name__ == "__main__":
numbers = [3, -1, 2, -2, 5, -3, 4, -4]
result = stable_partition(numbers)
print(result)
# Output: [3, 2, 5, 4, -1, -2, -3, -4]Let n be the number of elements. The outer loop visits n positions. A positive number may also shift several earlier negative numbers. In the worst case, the total number of shifts grows like 1 + 2 + 3 and so on. Therefore, the worst-case time complexity is O(n²). The algorithm uses only first_negative, loop indices, and one saved value. It does not create another array. Therefore, the auxiliary space complexity is O(1).
This pattern is useful when data must be grouped while keeping the original order inside each group. Examples include moving valid records before invalid records, placing active items before inactive items, or grouping events by a condition when stable order matters and extra memory is limited.
The interviewer is checking whether you understand the difference between ordinary partitioning and stable partitioning. They want to see whether you can preserve relative order without using another array. The problem also tests careful in-place updates, loop direction, invariant reasoning, and accurate complexity analysis. A strong answer explains why simple swapping fails, why shifting preserves order, and why the O(1) space requirement causes the worst-case time to become O(n²).
A common mistake is swapping each positive value with the first negative value. That can change the relative order of the negative numbers. Another mistake is forgetting to save the positive value before shifting, which can overwrite it. Candidates may also shift in the wrong direction. The shift must move from right to left so unread values are not destroyed. Another mistake is incrementing first_negative before the insertion is complete. Finally, claiming O(n) time is incorrect because the inner shifting loop can run many times.
State the invariant before coding: first_negative marks the start of the stable negative block. Then explain that every later positive value is inserted at that position while the whole negative block shifts right.









