https://leetcode.com/problems/wiggle-sort-ii/#/description
一开始想的方法是把数组复制一份,然后按最小、最大、最小...依次放数字,结果WA,错误代码:
class Solution {
public:
void wiggleSort(vector<int>& nums) {
int n = nums.size();
if (n == 0) return;
vector<int> nums_copy(nums.begin(), nums.end());
sort(nums_copy.begin(), nums_copy.end());
int k = 0, p = 0, q = n - 1;
bool sign = true; // smallest of the remaining numbers
while (k < n) {
if (sign) nums[k++] = nums_copy[p++];
else nums[k++] = nums_copy[q--];
sign = !sign;
}
}
};
比如下面这个例子:[1,3,2,2,3,1]
按这个方法得到的输出是[1,3,1,3,2,2]
,最后两个数不符合要求。在做的过程中,最小和最大数的差距逐渐缩小,导致最后无法达成严格的大小关系。可见这种方法最大的困难在于如何避免连续两个数相等(甚至可行解就很少)。
如果取完最小的,不取当前最大,取一个比较大的数?比如,这个“比较大的数”取排序完靠中间的。上面的例子,排序后是1,1,2, | 2,3,3
,取右半边第一个数。但是还是有问题,比如4,5,5,6
,按这个方法得到的是4,5,5,6
,中间两个又相等了,当排序后中位数不止一个时,就会有这个问题。有效的克服办法是,小的从中间开始递减着取,大的从最大开始递减着取:
class Solution {
public:
void wiggleSort(vector<int>& nums) {
int n = nums.size();
if (n == 0) return;
vector<int> nums_copy(nums.begin(), nums.end());
sort(nums_copy.begin(), nums_copy.end());
int k = 0, p = (n-1)/2, q = n-1;
bool sign = true; // smallest of the remaining numbers
while (k < n) {
if (sign) nums[k++] = nums_copy[p--];
else nums[k++] = nums_copy[q--];
sign = !sign;
}
}
};
能不能优化?比如,完整的排序有没有必要?再看上面的操作,其实我们是想把整个数组划分成左右两部分,使得左边的比右边的小,然后左、右交替着取。这样就自然联想到了快速排序的partition操作,但是这里最好能够将划分做得均衡,因此可以求kth largest element in an array
( https://leetcode.com/problems/kth-largest-element-in-an-array/ )。另外,除了均衡,仍然要克服中位数不止一个的情形,比如类似4,5,5,5,5,6,6,6
的case,因此在做完划分最好将中位数调整一下位置:一种可行方法是把中位数全部挤到中间。
下面的参考代码,偷懒一下,划分的操作就用std::nth_element
代替了。其他的部分都没变,只是把原来的排序换成了nth_element()
+ refine()
,其中refine()
就是调整中位数位置的操作。
时间复杂度:平均意义上是O(n)
空间复杂度:O(n),O(1)的做法是在做完划分后交替着用交换构造结果
class Solution {
public:
void wiggleSort(vector<int>& nums) {
int n = nums.size();
if (n == 0) return;
vector<int> nums_copy(nums.begin(), nums.end());
int mid = (n - 1) / 2;
std::nth_element(nums_copy.begin(), nums_copy.begin() + mid, nums_copy.end());
refine(nums_copy, mid);
int k = 0, p = mid, q = n-1;
bool sign = true; // need a smaller number
while (k < n) {
if (sign) nums[k++] = nums_copy[p--];
else nums[k++] = nums_copy[q--];
sign = !sign;
}
}
private:
void refine(vector<int>& nums, int mid) {
int put_here = 0, mid_val = nums[mid];
// left part
for (int i = 0; i < mid; ++i) {
if (nums[i] != mid_val) nums[put_here++] = nums[i];
}
while (put_here < mid) nums[put_here++] = mid_val;
// right part
put_here = nums.size() - 1;
for (int i = nums.size()-1; i > mid; --i) {
if (nums[i] != mid_val) nums[put_here--] = nums[i];
}
while (put_here > mid) nums[put_here--] = mid_val;
}
};