324. Wiggle Sort II (Medium)

Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3]....

Example:

(1) Given nums = [1, 5, 1, 1, 6, 4], one possible answer is [1, 4, 1, 5, 1, 6].

(2) Given nums = [1, 3, 2, 2, 3, 1], one possible answer is [2, 3, 1, 3, 1, 2].

Note:

You may assume all input has valid answer.

Follow Up:

Can you do it in O(n) time and/or in-place with O(1) extra space?

Solution 1:

Time Complexity: $$O(nlogn)$$

这道题给了我们一个无序数组,让我们排序成摆动数组,满足nums[0] < nums[1] > nums[2] < nums[3]...,并给了我们例子。我们可以先给数组排序,然后在做调整。调整的方法是找到数组的中间的数,相当于把有序数组从中间分成两部分,然后从前半段的末尾取一个,在从后半的末尾去一个,这样保证了第一个数小于第二个数,然后从前半段取倒数第二个,从后半段取倒数第二个,这保证了第二个数大于第三个数,且第三个数小于第四个数,以此类推直至都取完,参见代码如下:

Example nums = [1,2,...,7]      Example nums = [1,2,...,8] 

Small half:  4 . 3 . 2 . 1      Small half:  4 . 3 . 2 . 1 .
Large half:  . 7 . 6 . 5 .      Large half:  . 8 . 7 . 6 . 5
--------------------------      --------------------------
Together:    4 7 3 6 2 5 1      Together:    4 8 3 7 2 6 1 5
void wiggleSort(vector<int>& nums) {
    vector<int> tmp(nums);
    sort(tmp.begin(),tmp.end());
    int n = nums.size();
    for (int i = 0, l = (n+1)/2, r = n; i < n; ++i) {
        nums[i] = i & 1 ? tmp[--r] : tmp[--l];
    }
}

Solution 2:

Complexities: (On the condition that finding median is O(n)-time and O(1)-space)

Time: $$O(n)$$

Space: $$O(1)$$

First I find a median using nth_element. That only guarantees O(n) average time complexity and I don't know about space complexity. I might write this myself using O(n) time and O(1) space, but that's not what I want to show here.

This post is about what comes after that. We can use three-way partitioning to arrange the numbers so that those larger than the median come first, then those equal to the median come next, and then those smaller than the median come last.

Ordinarily, you'd then use one more phase to bring the numbers to their final positions to reach the overall wiggle-property. But I don't know a nice O(1) space way for this. Instead, I embed this right into the partitioning algorithm. That algorithm simply works with indexes 0 to n-1 as usual, but sneaky as I am, I rewire those indexes where I want the numbers to actually end up. The partitioning-algorithm doesn't even know that I'm doing that, it just works like normal (it just uses A(x) instead of nums[x]).

Let's say nums is [10,11,...,19]. Then after nth_element and ordinary partitioning, we might have this (15 is my median):

index:     0  1  2  3   4   5  6  7  8  9
number:   18 17 19 16  15  11 14 10 13 12

I rewire it so that the first spot has index 5, the second spot has index 0, etc, so that I might get this instead:

index:     5  0  6  1  7  2  8  3  9  4
number:   11 18 14 17 10 19 13 16 12 15

And 11 18 14 17 10 19 13 16 12 15 is perfectly wiggly. And the whole partitioning-to-wiggly-arrangement (everything after finding the median) only takes O(n) time and O(1) space.

If the above description is unclear, maybe this explicit listing helps:

Accessing A(0) actually accesses nums[1].
Accessing A(1) actually accesses nums[3].
Accessing A(2) actually accesses nums[5].
Accessing A(3) actually accesses nums[7].
Accessing A(4) actually accesses nums[9].
Accessing A(5) actually accesses nums[0].
Accessing A(6) actually accesses nums[2].
Accessing A(7) actually accesses nums[4].
Accessing A(8) actually accesses nums[6].
Accessing A(9) actually accesses nums[8].

Methodology:

Idea 1.

As @whnzinc pointed out in this thread, all elements in nums can be classified into three categories:

(1) Larger than the median;

(2) Equal to the median;

(3) Smaller than the median.

Note that it's possible to find the median within O(n)-time and O(1)-space.

Note: We can use nth_element to find the median, but it's not O(n)-time and O(1)-space. For the sake of simplicity, I might use nth_element as well.

Idea 2.

As @StefanPochmann pointed out in this thread, we can arrange the elements in the three categories in a deterministic way.

(1) Elements that are larger than the median: we can put them in the first few odd slots;

(2) Elements that are smaller than the median: we can put them in the last few even slots;

(3) Elements that equal the median: we can put them in the remaining slots.

Example:

Original Indices:    0  1  2  3  4  5  6  7  8  9 10 11
Mapped Indices:      1  3  5  7  9 11  0  2  4  6  8 10

(its reverse mapping is)

Mapped Indices:      0  1  2  3  4  5  6  7  8  9 10 11
Original Indices:    6  0  7  1  8  2  9  3 10  4 11  5   (wiggled)

In order to achieve this, we can use a function alike

int map_index(int idx, int n) {
    return (2 * idx + 1) % (n | 1);
}

where (n | 1) calculates the nearest odd that is not less than n.

we can use a one-pass three-way partition to rearrange all elements. His idea is to re-map the indices into its destined indices, odd indices first and even indices follow.

Note:

put large numbers at position 1,3,5,7,9

medium at 0

put small numbers at 2,4,6,8

void wiggleSort(vector<int>& nums) {
    int n = nums.size();

    // Find a median.
    auto midptr = nums.begin()+n/2;
    nth_element(nums.begin(), midptr, nums.end());
    int median = *midptr;

    // Index-rewiring.
    #define A(i) nums[(1+2*i) % (n|1)]

    // 3-way-partition-to-wiggly in O(n) time with O(1) space.
    int first = 0, mid = 0, last = n - 1;
    while (mid <= last) {
        if (A(mid) > median) swap(A(first++), A(mid++));
        else if (A(mid) < median) swap(A(mid), A(last--));
        else ++mid;
    }
}

results matching ""

    No results matching ""