324. Wiggle Sort II
Problem Description
You are given an integer array nums
. Your task is to reorder the array elements in-place so that they follow a "wiggle" pattern where:
- The element at even index is smaller than the element at the next odd index
- The element at odd index is greater than the element at the next even index
In other words, the pattern should be: nums[0] < nums[1] > nums[2] < nums[3] > nums[4] < nums[5]...
The problem guarantees that the input array will always have a valid answer, meaning it's always possible to arrange the elements to satisfy this wiggle pattern.
The solution approach sorts the array first, then strategically places elements from two different portions of the sorted array. It uses two pointers: one starting from the middle of the sorted array moving backward (for even indices), and another starting from the end of the sorted array moving backward (for odd indices). This ensures that smaller elements from the first half are placed at even positions, and larger elements from the second half are placed at odd positions, creating the required wiggle pattern.
For example, if the sorted array is [1, 2, 3, 4, 5, 6]
, the algorithm would place elements like:
- Even indices (0, 2, 4): get elements from position
(n-1)/2
backward:3, 2, 1
- Odd indices (1, 3, 5): get elements from position
n-1
backward:6, 5, 4
- Result:
[3, 6, 2, 5, 1, 4]
which satisfies3 < 6 > 2 < 5 > 1 < 4
Intuition
To create a wiggle pattern where elements alternate between being smaller and larger than their neighbors, we need to carefully think about how to distribute the values in the array.
The key insight is that if we sort the array first, we can divide it into two halves: smaller elements and larger elements. To avoid having equal adjacent elements (which would violate the strict inequality requirement), we need to interleave these two halves in a specific way.
Why does simply alternating between small and large halves work? Consider a sorted array like [1, 2, 3, 4, 5, 6]
. If we naively take elements from the beginning of each half ([1, 2, 3]
and [4, 5, 6]
) and alternate them, we might get [1, 4, 2, 5, 3, 6]
. This works, but there's a subtle issue: if we have duplicate values around the middle (like [1, 2, 3, 3, 4, 5]
), placing elements from the start of each half could put equal values next to each other.
The clever trick is to take elements from the end of each half, moving backward. By taking from the middle position (n-1)/2
backward for even indices and from the last position n-1
backward for odd indices, we maximize the "distance" between selected values. This ensures that:
- Elements at even positions come from the smaller half (but starting from its largest values)
- Elements at odd positions come from the larger half (starting from its largest values)
- Adjacent elements are as far apart as possible in the sorted order, minimizing the chance of placing equal values next to each other
This backward traversal strategy guarantees that we create valid "peaks" and "valleys" in the wiggle pattern, with each peak being strictly greater than its neighboring valleys, and each valley being strictly less than its neighboring peaks.
Solution Approach
The implementation follows a three-step process:
Step 1: Sort the array
arr = sorted(nums)
We create a sorted copy of the input array. This allows us to identify which elements should go into the "smaller" half and which into the "larger" half.
Step 2: Initialize two pointers
i, j = (n - 1) >> 1, n - 1
- Pointer
i
starts at the middle index(n-1) >> 1
(equivalent to(n-1) // 2
). This pointer will traverse the first half of the sorted array backward. - Pointer
j
starts at the last indexn - 1
. This pointer will traverse the second half of the sorted array backward.
Step 3: Place elements alternately
for k in range(n):
if k % 2 == 0:
nums[k] = arr[i]
i -= 1
else:
nums[k] = arr[j]
j -= 1
We iterate through each position in the original array:
- For even indices (
k % 2 == 0
): Place elements from the first half by using pointeri
, then decrementi
- For odd indices: Place elements from the second half by using pointer
j
, then decrementj
Example walkthrough:
For nums = [1, 5, 1, 1, 6, 4]
:
- After sorting:
arr = [1, 1, 1, 4, 5, 6]
- Initialize:
i = 2
(pointing toarr[2] = 1
),j = 5
(pointing toarr[5] = 6
) - Fill positions:
k=0
(even):nums[0] = arr[2] = 1
,i
becomes 1k=1
(odd):nums[1] = arr[5] = 6
,j
becomes 4k=2
(even):nums[2] = arr[1] = 1
,i
becomes 0k=3
(odd):nums[3] = arr[4] = 5
,j
becomes 3k=4
(even):nums[4] = arr[0] = 1
,i
becomes -1k=5
(odd):nums[5] = arr[3] = 4
,j
becomes 2
- Result:
nums = [1, 6, 1, 5, 1, 4]
which satisfies1 < 6 > 1 < 5 > 1 < 4
The time complexity is O(n log n)
due to sorting, and the space complexity is O(n)
for storing the sorted array.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with nums = [3, 5, 2, 1, 6, 4]
to illustrate the solution approach.
Step 1: Sort the array First, we create a sorted copy:
arr = [1, 2, 3, 4, 5, 6]
Step 2: Initialize two pointers
n = 6 i = (6-1) >> 1 = 2 (points to arr[2] = 3) j = 6-1 = 5 (points to arr[5] = 6)
Step 3: Place elements alternately Now we fill each position in the original array:
k | k%2 | Action | Element placed | i | j | nums so far |
---|---|---|---|---|---|---|
0 | 0 (even) | Use arr[i=2] | 3 | 1 | 5 | [3, _, _, _, _, _] |
1 | 1 (odd) | Use arr[j=5] | 6 | 1 | 4 | [3, 6, _, _, _, _] |
2 | 0 (even) | Use arr[i=1] | 2 | 0 | 4 | [3, 6, 2, _, _, _] |
3 | 1 (odd) | Use arr[j=4] | 5 | 0 | 3 | [3, 6, 2, 5, _, _] |
4 | 0 (even) | Use arr[i=0] | 1 | -1 | 3 | [3, 6, 2, 5, 1, _] |
5 | 1 (odd) | Use arr[j=3] | 4 | -1 | 2 | [3, 6, 2, 5, 1, 4] |
Final result: nums = [3, 6, 2, 5, 1, 4]
Verification of wiggle pattern:
- nums[0] < nums[1]: 3 < 6 β
- nums[1] > nums[2]: 6 > 2 β
- nums[2] < nums[3]: 2 < 5 β
- nums[3] > nums[4]: 5 > 1 β
- nums[4] < nums[5]: 1 < 4 β
The key insight here is that by taking elements from the end of each half moving backward, we ensure maximum separation between adjacent elements. The smaller half contributes {3, 2, 1} to even positions, while the larger half contributes {6, 5, 4} to odd positions, creating the required alternating pattern of peaks and valleys.
Solution Implementation
1class Solution:
2 def wiggleSort(self, nums: List[int]) -> None:
3 """
4 Do not return anything, modify nums in-place instead.
5 """
6 # Create a sorted copy of the input array
7 sorted_array = sorted(nums)
8 n = len(sorted_array)
9
10 # Initialize two pointers:
11 # left_pointer: points to the middle element (for smaller half)
12 # right_pointer: points to the last element (for larger half)
13 left_pointer = (n - 1) // 2
14 right_pointer = n - 1
15
16 # Reconstruct the array in wiggle pattern
17 # Even indices get elements from the smaller half (backwards from middle)
18 # Odd indices get elements from the larger half (backwards from end)
19 for index in range(n):
20 if index % 2 == 0:
21 # Place smaller elements at even positions
22 nums[index] = sorted_array[left_pointer]
23 left_pointer -= 1
24 else:
25 # Place larger elements at odd positions
26 nums[index] = sorted_array[right_pointer]
27 right_pointer -= 1
28
1class Solution {
2 /**
3 * Rearranges the array into a wiggle pattern where nums[0] < nums[1] > nums[2] < nums[3]...
4 * Algorithm:
5 * 1. Create a sorted copy of the input array
6 * 2. Split conceptually into two halves: smaller half and larger half
7 * 3. Place elements from each half alternately, starting with smaller half
8 * 4. Traverse both halves from right to left to avoid equal adjacent elements
9 *
10 * @param nums the input array to be rearranged in-place
11 */
12 public void wiggleSort(int[] nums) {
13 // Create a copy of the original array and sort it
14 int[] sortedArray = nums.clone();
15 Arrays.sort(sortedArray);
16
17 // Get the length of the array
18 int length = nums.length;
19
20 // Calculate the middle index (end of smaller half)
21 // For odd length, smaller half has one more element
22 int middleIndex = (length - 1) >> 1; // Equivalent to (length - 1) / 2
23
24 // Index for the larger half (starts from the last element)
25 int largerHalfIndex = length - 1;
26
27 // Fill the original array with wiggle pattern
28 for (int currentIndex = 0; currentIndex < length; currentIndex++) {
29 if (currentIndex % 2 == 0) {
30 // Even indices get elements from the smaller half (traversing right to left)
31 nums[currentIndex] = sortedArray[middleIndex];
32 middleIndex--;
33 } else {
34 // Odd indices get elements from the larger half (traversing right to left)
35 nums[currentIndex] = sortedArray[largerHalfIndex];
36 largerHalfIndex--;
37 }
38 }
39 }
40}
41
1class Solution {
2public:
3 void wiggleSort(vector<int>& nums) {
4 // Create a copy of the original array
5 vector<int> sortedArray = nums;
6
7 // Sort the copied array in ascending order
8 sort(sortedArray.begin(), sortedArray.end());
9
10 // Get the size of the array
11 int arraySize = nums.size();
12
13 // Initialize two pointers:
14 // leftPointer: points to the middle element (for smaller half)
15 // rightPointer: points to the last element (for larger half)
16 int leftPointer = (arraySize - 1) / 2;
17 int rightPointer = arraySize - 1;
18
19 // Reconstruct the array in wiggle pattern
20 // Even indices get elements from the smaller half (in reverse order)
21 // Odd indices get elements from the larger half (in reverse order)
22 for (int index = 0; index < arraySize; ++index) {
23 if (index % 2 == 0) {
24 // Even index: pick from smaller half
25 nums[index] = sortedArray[leftPointer];
26 leftPointer--;
27 } else {
28 // Odd index: pick from larger half
29 nums[index] = sortedArray[rightPointer];
30 rightPointer--;
31 }
32 }
33 }
34};
35
1/**
2 * Wiggle sort function that rearranges the array such that nums[0] < nums[1] > nums[2] < nums[3]...
3 * Uses bucket sort approach to achieve the wiggle pattern
4 * @param nums - The input array to be sorted in wiggle pattern
5 * @return void - Modifies the array in-place
6 */
7function wiggleSort(nums: number[]): void {
8 // Create a bucket array to count frequency of each number (0 to 5000)
9 const bucket: number[] = new Array(5001).fill(0);
10
11 // Count the frequency of each number in the input array
12 for (const value of nums) {
13 bucket[value]++;
14 }
15
16 const arrayLength: number = nums.length;
17 let currentIndex: number = 5000;
18
19 // Fill odd indices with larger numbers from the bucket (descending order)
20 // This ensures nums[1], nums[3], nums[5]... get larger values
21 for (let i = 1; i < arrayLength; i += 2) {
22 // Skip empty buckets
23 while (bucket[currentIndex] === 0) {
24 currentIndex--;
25 }
26 nums[i] = currentIndex;
27 bucket[currentIndex]--;
28 }
29
30 // Fill even indices with remaining numbers from the bucket (still descending)
31 // This ensures nums[0], nums[2], nums[4]... get smaller values than odd indices
32 for (let i = 0; i < arrayLength; i += 2) {
33 // Skip empty buckets
34 while (bucket[currentIndex] === 0) {
35 currentIndex--;
36 }
37 nums[i] = currentIndex;
38 bucket[currentIndex]--;
39 }
40}
41
Time and Space Complexity
Time Complexity: O(n log n)
The dominant operation in this algorithm is the sorting step arr = sorted(nums)
, which takes O(n log n)
time where n
is the length of the input array. After sorting, the algorithm performs a single pass through the array to rearrange elements, which takes O(n)
time. Therefore, the overall time complexity is O(n log n) + O(n) = O(n log n)
.
Space Complexity: O(n)
The algorithm creates a sorted copy of the input array arr = sorted(nums)
, which requires O(n)
additional space to store all n
elements. The remaining variables (i
, j
, k
, n
) use only O(1)
constant space. Therefore, the total space complexity is O(n)
.
Common Pitfalls
Pitfall: Failing to Handle Duplicate Elements Correctly
The most critical pitfall in this problem occurs when the array contains many duplicate elements, especially when duplicates appear around the median value. Simply placing elements from the sorted array's two halves might create adjacent equal elements, violating the strict inequality requirement of the wiggle pattern.
Problem Example:
Consider nums = [4, 5, 5, 6]
:
- Sorted:
[4, 5, 5, 6]
- Using a naive approach might produce:
[5, 6, 4, 5]
- This violates the pattern at indices 2-3:
4 < 5
but we need4 > 5
Why This Happens: When duplicate values exist near the median, they can end up in both the "smaller" and "larger" halves. The backward traversal strategy helps mitigate this by maximizing the distance between where duplicates are placed, but understanding this is crucial.
Solution: Understanding the Backward Traversal Strategy
The algorithm deliberately traverses both halves backward to create maximum separation between potential duplicate values:
def wiggleSort(self, nums: List[int]) -> None:
sorted_array = sorted(nums)
n = len(sorted_array)
# Key insight: Start from the rightmost elements of each half
# This maximizes distance between duplicates
mid = (n - 1) // 2 # Last index of smaller half
end = n - 1 # Last index of larger half
# Fill the array alternating between halves, going backward
for i in range(n):
if i % 2 == 0:
nums[i] = sorted_array[mid]
mid -= 1
else:
nums[i] = sorted_array[end]
end -= 1
Visual Example with Duplicates:
For nums = [1, 1, 2, 2, 3, 3]
:
- Sorted:
[1, 1, 2, 2, 3, 3]
- Mid pointer starts at index 2 (value 2)
- End pointer starts at index 5 (value 3)
- Result:
[2, 3, 1, 3, 1, 2]
β (satisfies wiggle pattern)
If we had used forward traversal instead:
- Would produce:
[1, 2, 1, 2, 2, 3]
β (fails at indices 3-4:2 = 2
)
Additional Considerations:
-
Array Length: The algorithm works correctly for both even and odd length arrays due to the
(n-1)//2
calculation for the middle index. -
Edge Case - All Same Elements: While the problem guarantees a valid answer exists, arrays with all identical elements (like
[5, 5, 5, 5]
) cannot form a valid wiggle pattern. The problem statement ensures this won't occur. -
Alternative O(n) Solution: For those seeking optimal time complexity, there exists a more complex O(n) solution using virtual indexing and three-way partitioning (Dutch National Flag algorithm variant), but it's significantly harder to implement and understand.
How does merge sort divide the problem into subproblems?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
https assets algo monster cover_photos dnd svg Divide and Conquer The main idea of divide and conquer is to split the main problem into two smaller components assuming that each one of the components had already been solved recursively and then try to solve the bigger problem using the solutions
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Donβt Miss This!