🧠
Median Finding + Virtual Indexing + Three-Way Partitioning
💡 This approach improves on the brute force by using the median to partition the array in-place, avoiding extra space and ensuring the wiggle property. It is more complex but efficient.
Intuition
Find the median, then reorder elements so that numbers greater than the median go to odd indices and smaller ones to even indices, using a virtual indexing scheme to avoid conflicts.
Algorithm
- Find the median of the array using a linear-time selection algorithm.
- Define a virtual index mapping to rearrange indices for wiggle placement.
- Use three-way partitioning (Dutch National Flag) to reorder elements around the median.
- After partitioning, the array satisfies the wiggle property in-place.
💡 The key difficulty is understanding the virtual indexing and how it helps place elements correctly without extra space. The three-way partitioning separates elements into less than, equal to, and greater than median groups.
def wiggleSort(nums):
n = len(nums)
def nth_element(nums, n):
import random
def select(left, right, n_smallest):
if left == right:
return nums[left]
pivot_index = random.randint(left, right)
pivot_index = partition(left, right, pivot_index)
if n_smallest == pivot_index:
return nums[n_smallest]
elif n_smallest < pivot_index:
return select(left, pivot_index - 1, n_smallest)
else:
return select(pivot_index + 1, right, n_smallest)
def partition(left, right, pivot_index):
pivot = nums[pivot_index]
nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
store_index = left
for i in range(left, right):
if nums[i] < pivot:
nums[store_index], nums[i] = nums[i], nums[store_index]
store_index += 1
nums[right], nums[store_index] = nums[store_index], nums[right]
return store_index
return select(0, len(nums) - 1, n)
median = nth_element(nums, (n - 1) // 2)
def virtual_index(i):
return (1 + 2 * i) % (n | 1)
left, i, right = 0, 0, n - 1
while i <= right:
vi = virtual_index(i)
if nums[vi] > median:
vleft = virtual_index(left)
nums[vi], nums[vleft] = nums[vleft], nums[vi]
left += 1
i += 1
elif nums[vi] < median:
vright = virtual_index(right)
nums[vi], nums[vright] = nums[vright], nums[vi]
right -= 1
else:
i += 1
# Driver code
if __name__ == '__main__':
arr = [1, 5, 1, 1, 6, 4]
wiggleSort(arr)
print(arr)
Line Notes
def nth_element(nums, n):Function to find nth smallest element (median) in average linear time using quickselect.
pivot_index = random.randint(left, right)Random pivot selection to improve average case performance and avoid worst-case.
if nums[i] < pivot:Partition elements less than pivot to the left side.
median = nth_element(nums, (n - 1) // 2)Find median element to partition array around for wiggle sorting.
def virtual_index(i):Map original indices to virtual indices to place elements in wiggle order without conflicts.
return (1 + 2 * i) % (n | 1)Ensure odd indices are filled first, wrapping around to cover all indices.
while i <= right:Three-way partition loop to reorder elements around median.
if nums[vi] > median:Place elements greater than median at the front (odd indices).
nums[vi], nums[vleft] = nums[vleft], nums[vi]Swap elements to correct position according to virtual index.
elif nums[vi] < median:Place elements less than median at the end (even indices).
nums[vi], nums[vright] = nums[vright], nums[vi]Swap elements to correct position according to virtual index.
else:Elements equal to median remain in the middle section.
import java.util.*;
public class Solution {
public void wiggleSort(int[] nums) {
int n = nums.length;
int median = findKthSmallest(nums, (n - 1) / 2);
int left = 0, i = 0, right = n - 1;
while (i <= right) {
int vi = virtualIndex(i, n);
if (nums[vi] > median) {
int vleft = virtualIndex(left, n);
swap(nums, vi, vleft);
left++;
i++;
} else if (nums[vi] < median) {
int vright = virtualIndex(right, n);
swap(nums, vi, vright);
right--;
} else {
i++;
}
}
}
private int virtualIndex(int i, int n) {
return (1 + 2 * i) % (n | 1);
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private int findKthSmallest(int[] nums, int k) {
return quickSelect(nums, 0, nums.length - 1, k);
}
private int quickSelect(int[] nums, int left, int right, int k) {
if (left == right) return nums[left];
Random rand = new Random();
int pivotIndex = left + rand.nextInt(right - left + 1);
pivotIndex = partition(nums, left, right, pivotIndex);
if (k == pivotIndex) return nums[k];
else if (k < pivotIndex) return quickSelect(nums, left, pivotIndex - 1, k);
else return quickSelect(nums, pivotIndex + 1, right, k);
}
private int partition(int[] nums, int left, int right, int pivotIndex) {
int pivot = nums[pivotIndex];
swap(nums, pivotIndex, right);
int storeIndex = left;
for (int i = left; i < right; i++) {
if (nums[i] < pivot) {
swap(nums, storeIndex, i);
storeIndex++;
}
}
swap(nums, storeIndex, right);
return storeIndex;
}
public static void main(String[] args) {
Solution sol = new Solution();
int[] arr = {1, 5, 1, 1, 6, 4};
sol.wiggleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
Line Notes
int median = findKthSmallest(nums, (n - 1) / 2);Find median element using quickselect to partition array efficiently.
while (i <= right) {Three-way partition loop to reorder elements around median.
int vi = virtualIndex(i, n);Map index to virtual index for correct wiggle placement.
if (nums[vi] > median) {Place elements greater than median at front (odd indices).
swap(nums, vi, vleft);Swap elements to correct position according to virtual index.
else if (nums[vi] < median) {Place elements less than median at end (even indices).
swap(nums, vi, vright);Swap elements to correct position according to virtual index.
private int virtualIndex(int i, int n) {Virtual index mapping function to reorder indices.
private int findKthSmallest(int[] nums, int k) {Quickselect to find kth smallest element (median).
private int partition(int[] nums, int left, int right, int pivotIndex) {Partition helper for quickselect.
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
using namespace std;
class Solution {
public:
int virtualIndex(int i, int n) {
return (1 + 2 * i) % (n | 1);
}
void swap(vector<int>& nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
int partition(vector<int>& nums, int left, int right, int pivotIndex) {
int pivot = nums[pivotIndex];
swap(nums, pivotIndex, right);
int storeIndex = left;
for (int i = left; i < right; i++) {
if (nums[i] < pivot) {
swap(nums, storeIndex, i);
storeIndex++;
}
}
swap(nums, storeIndex, right);
return storeIndex;
}
int quickSelect(vector<int>& nums, int left, int right, int k) {
if (left == right) return nums[left];
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<> dis(left, right);
int pivotIndex = dis(gen);
pivotIndex = partition(nums, left, right, pivotIndex);
if (k == pivotIndex) return nums[k];
else if (k < pivotIndex) return quickSelect(nums, left, pivotIndex - 1, k);
else return quickSelect(nums, pivotIndex + 1, right, k);
}
void wiggleSort(vector<int>& nums) {
int n = nums.size();
int median = quickSelect(nums, 0, n - 1, (n - 1) / 2);
int left = 0, i = 0, right = n - 1;
while (i <= right) {
int vi = virtualIndex(i, n);
if (nums[vi] > median) {
int vleft = virtualIndex(left, n);
swap(nums, vi, vleft);
left++;
i++;
} else if (nums[vi] < median) {
int vright = virtualIndex(right, n);
swap(nums, vi, vright);
right--;
} else {
i++;
}
}
}
};
int main() {
vector<int> arr = {1, 5, 1, 1, 6, 4};
Solution sol;
sol.wiggleSort(arr);
for (int num : arr) cout << num << " ";
cout << endl;
return 0;
}
Line Notes
int virtualIndex(int i, int n) {Virtual index mapping to reorder indices for wiggle placement.
int partition(vector<int>& nums, int left, int right, int pivotIndex) {Partition function for quickselect to find median.
int quickSelect(vector<int>& nums, int left, int right, int k) {Quickselect to find median element efficiently.
int median = quickSelect(nums, 0, n - 1, (n - 1) / 2);Find median element to partition around.
while (i <= right) {Three-way partition loop to reorder elements around median.
if (nums[vi] > median) {Place elements greater than median at front (odd indices).
swap(nums, vi, vleft);Swap elements to correct position according to virtual index.
else if (nums[vi] < median) {Place elements less than median at end (even indices).
swap(nums, vi, vright);Swap elements to correct position according to virtual index.
else {Elements equal to median stay in the middle.
var wiggleSort = function(nums) {
const n = nums.length;
function quickSelect(arr, left, right, k) {
if (left === right) return arr[left];
const pivotIndex = left + Math.floor(Math.random() * (right - left + 1));
const pivotNewIndex = partition(arr, left, right, pivotIndex);
if (k === pivotNewIndex) return arr[k];
else if (k < pivotNewIndex) return quickSelect(arr, left, pivotNewIndex - 1, k);
else return quickSelect(arr, pivotNewIndex + 1, right, k);
}
function partition(arr, left, right, pivotIndex) {
const pivot = arr[pivotIndex];
[arr[pivotIndex], arr[right]] = [arr[right], arr[pivotIndex]];
let storeIndex = left;
for (let i = left; i < right; i++) {
if (arr[i] < pivot) {
[arr[storeIndex], arr[i]] = [arr[i], arr[storeIndex]];
storeIndex++;
}
}
[arr[right], arr[storeIndex]] = [arr[storeIndex], arr[right]];
return storeIndex;
}
function virtualIndex(i) {
return (1 + 2 * i) % (n | 1);
}
const median = quickSelect(nums, 0, n - 1, Math.floor((n - 1) / 2));
let left = 0, i = 0, right = n - 1;
while (i <= right) {
const vi = virtualIndex(i);
if (nums[vi] > median) {
const vleft = virtualIndex(left);
[nums[vi], nums[vleft]] = [nums[vleft], nums[vi]];
left++;
i++;
} else if (nums[vi] < median) {
const vright = virtualIndex(right);
[nums[vi], nums[vright]] = [nums[vright], nums[vi]];
right--;
} else {
i++;
}
}
};
// Driver code
const arr = [1,5,1,1,6,4];
wiggleSort(arr);
console.log(arr);
Line Notes
function quickSelect(arr, left, right, k) {Quickselect to find kth smallest element (median) efficiently.
const pivotIndex = left + Math.floor(Math.random() * (right - left + 1));Random pivot selection to improve average case performance.
function partition(arr, left, right, pivotIndex) {Partition helper for quickselect to reorder elements.
function virtualIndex(i) {Virtual index mapping to reorder indices for wiggle placement.
const median = quickSelect(nums, 0, n - 1, Math.floor((n - 1) / 2));Find median element to partition around.
while (i <= right) {Three-way partition loop to reorder elements around median.
if (nums[vi] > median) {Place elements greater than median at front (odd indices).
[nums[vi], nums[vleft]] = [nums[vleft], nums[vi]];Swap elements to correct position according to virtual index.
else if (nums[vi] < median) {Place elements less than median at end (even indices).
[nums[vi], nums[vright]] = [nums[vright], nums[vi]];Swap elements to correct position according to virtual index.
else {Elements equal to median stay in the middle.
TimeO(n) average
SpaceO(1)
Median finding via quickselect is O(n) average, and three-way partitioning is O(n). No extra arrays used, so space is constant.
💡 For n=100,000, this approach is efficient and uses constant extra space, making it ideal for large inputs and interviews.
Interview Verdict: Accepted and optimal in average case
This is the preferred approach in interviews due to its efficiency and in-place operation, though it is more complex to implement.