Problem Description
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,3,5,7,9…),小的那一部分放在索引为偶数的位置(0,2,4,6,8…)。(这里是可以随意放的)
这个想法是对的,但还不够。比如当数组为[4,5,5,6]时,得到的结果就是[4,5,5,6],这不符合题目要求,正确的结果应该是[5,6,4,5]。这里问题出在两部分数中相同大小的数放在一起了。
为了避免这个问题,直观上来说,需要把较小的那一部分中比较大的数和较大的那一部分中比较小的数放的越远越好。(要做到这样有点难,因为这样需要两部分的数都有序,实际上一步划分之后两部分的数是无序的)
当然还一个简单的方法,那就是确保和中位数相等的数间隔放置的。
这里用到了three-way partitioning算法。
three-way partitioning
给定一个数组,和一个划分数组用的目标数,将数组划分为小于,等于,大于目标数三部分。
比如对于数组
[2,1,3,6,5,7,3]
,目标数为3,则划分结果应该为
[2,1,3,3,6,5,7]
。
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
vector<int> nums{2,1,3,6,5,7,3};
int beg=0, end=nums.size()-1, i=0;
int target=3;
while(i<=end){
if(nums[i]<target) swap(nums[i++],nums[beg++]);
else if(nums[i]>target) swap(nums[i], nums[end--]);
else ++i;
}
for_each(nums.begin(), nums.end(), [](int i){cout<<i<<" ";});
cout<<endl;
return 0;
}
看懂上面的代码之后就可以比较容易的理解下面的代码了。
class Solution
{
public:
void wiggleSort( vector<int>& nums )
{
if(nums.empty()) return;
int n = static_cast<int>(nums.size());
//find the median
auto midPtr = nums.begin()+ n/2;
nth_element(nums.begin(), midPtr, nums.end());
int median = *midPtr;
//lambda: remap the idnex
auto m = [n](int idx){return (2*idx+1)%(n|1);};
//3-way-partition
int beg = 0, end = n - 1, cur = 0;
while(cur <= end){
if( nums[m(cur)] > median ){
swap(nums[m(cur++)], nums[m(beg++)]);
}
else if(nums[m(cur)] < median){
swap(nums[m(cur)], nums[m(end--)]);
}
else ++cur;
}
}
};
lambda表达式是用来重新映射数组索引的。
Accessing m(0) actually accesses nums[1].
Accessing m(1) actually accesses nums[3].
Accessing m(2) actually accesses nums[5].
Accessing m(3) actually accesses nums[7].
Accessing m(4) actually accesses nums[9].
Accessing m(5) actually accesses nums[0].
Accessing m(6) actually accesses nums[2].
Accessing m(7) actually accesses nums[4].
Accessing m(8) actually accesses nums[6].
Accessing m(9) actually accesses nums[8].
这样的话,就自然而言在划分数组的时候,把较小的一部分放在了奇数位置,较大的一部分放在了偶数位置,并且因为每次都是间隔了一个数,中间相同的数就会被隔开。
参考
https://discuss.leetcode.com/topic/32929/o-n-o-1-after-median-virtual-indexing/2