I'm currently solving LeetCode problem 2161. Partition Array According to Given Pivot:
You are given a 0-indexed integer array
nums
and an integerpivot
. Rearrangenums
such that the following conditions are satisfied:
Every element less than
pivot
appears before every element greater thanpivot
.Every element equal to
pivot
appears in between the elements less than and greater thanpivot
.The relative order of the elements less than
pivot
and the elements greater thanpivot
is maintained.
- More formally, consider every
pi
,pj
wherepi
is the new position of theith
element andpj
is the new position of thejth
element. For elements less thanpivot
, ifi < j
andnums[i] < pivot
andnums[j] < pivot
, thenpi < pj
. Similarly for elements greater thanpivot
, ifi < j
andnums[i] > pivot
andnums[j] > pivot
, thenpi < pj
.Return
nums
after the rearrangement.Example:
Input:
nums = [9,12,5,10,14,3,10]
,pivot = 10
Output:[9,5,3,10,10,12,14]
A solution with O(n)
memory is trivial. I tried to come up with a O(1)
memory solution and approached a similar to insertion sort way:
public int[] pivotArray(int[] nums, int pivot) {
for(int i = 0; i < nums.length; i++){
if(nums[i] < pivot){
for(int j = i; j > 0 && nums[j - 1] >= pivot; j--){
int tmp = nums[j];
nums[j] = nums[j - 1];
nums[j - 1] = tmp;
}
}
if(nums[i] == pivot){
for(int j = i; j > 0 && nums[j - 1] > pivot; j--){
int tmp = nums[j];
nums[j] = nums[j - 1];
nums[j - 1] = tmp;
}
}
}
return nums;
}
The issue is the solution has O(n^2)
time complexity. Is there an algorithm to solve this faster than O(n^2)
? I'm not sure if O(n)
time and O(1)
space complexity is possible though.
With a slight variation on the answer of Matt Timmermans you could first remove the pivot values from the input array (packing the other values to the left side of it), then apply a O(𝑛log𝑛) divide-and-conquer algorithm to repeatedly merge two subarrays that are already partitioned by the pivot, rotating the center two partitions (of the four partitions). Then finally insert the removed pivot values back between the two partitions that remain.
To avoid consuming O(log𝑛) stack space, perform the divide-and-conquer algorithm in a bottom-up fashion (no recursion).
Here is an implementation:
class Solution {
void sliceRotateLeft(int[] arr, int start, int end, int shift) {
// Rotation in O(𝑛), where 𝑛 is the size of the array slice
int size = end - start;
if (size <= 1) return;
shift %= size;
if (shift == 0) return;
for (int count = 0, first = 0; count < size; first++, count++) {
int j = first;
int value = arr[start + j];
for (int i = j + shift; i != first; j = i, i = (i + shift) % size) {
arr[start + j] = arr[start + i];
count++;
}
arr[start + j] = value;
}
}
int sliceFindPivotIndex(int[] arr, int start, int end, int pivot) {
// Linear search. NB: binary search would be possible,
// but does not improve overall complexity.
while (start < end && arr[start] < pivot) start++;
return start;
}
public int[] pivotArray(int[] nums, int pivot) {
// Phase 1: pack all non-pivot values to the left
int len = 0; // Number of non-pivot values
for (int i = 0; i < nums.length; i++) {
if (nums[i] != pivot) nums[len++] = nums[i];
}
// Phase 2: partition non-pivot values into less-than, greater-than
// Use bottom up divide-and-conquer
for (int size = 1; size < len; size *= 2) {
// Merge two correctly partitioned chunks into one combined chunk
for (int start = 0; start + size < len; start += size * 2) {
// Get the offsets of the partitioned subarrays
int mid = start + size;
int end = Math.min(mid + size, len);
int left = sliceFindPivotIndex(nums, start, mid, pivot);
int right = sliceFindPivotIndex(nums, mid, end, pivot);
sliceRotateLeft(nums, left, right, mid - left);
}
}
// Phase 3: inject the pivot values
int pivotFrequency = nums.length - len;
int i;
for (i = len - 1; i >= 0 && nums[i] > pivot; i--) {
nums[i + pivotFrequency] = nums[i];
}
for (i++; pivotFrequency > 0; i++, pivotFrequency--) {
nums[i] = pivot;
}
return nums;
}
}