1. 思路
这个题目很容易得到思路:
- 找到中位数,以中位数分为前后两部分
- 在奇数位上插入前部分的数,在偶数位上插入后半部分的数。
但是需要注意的是,数组里面重复的数,特别是中位数有重复。例如3,4,4,5。如果我们按照上面的规则任意的插入很有可能出现3,5,4,4这样的结果。显然是不满足题意的。所以在上面的基础上进行补充:
- 找到中位数,以中位数分为小于中位数(前)、等于中位数(中)、大于中位数(后)三部分。(ps : 保证中位数在中间一起)
- 将中位数前、后两部分分别进行翻转。
- 依次序在奇数位上插入前部分的数,在偶数位上插入后半部分的数。
经过第二步的翻转之后再依次插入保证了中位数尽可能的不会在一起。(当题目保证有解的情况下,一定不会在一起)
有了思路,需要找到对应的算法:
步骤1,可以使用三分Partition算法在时间复杂度O(n),空间复杂度O(1)得到。详解Partition算法
步骤2,3 可以使用数组索引映射的方式,时间复杂度O(n), 空间复杂度O(1)。详解数组索引映射
结合以上两种方法可以在Partition算法的时候进行索引映射,这样Partition之后的结果就是结果。
2.映射关系
这里先看看映射关系,对于:
1,2,3,3,4,6
先进行翻转:
3,2,1,6,4,3
然后依次奇偶插入:
3,6,2,4,1,3
所以映射为:
0→4
1→2
2→0
3→5
4→3
5→1
映射关系式为:
y
=
f
(
x
)
=
(
2
∗
(
(
n
∣
1
)
−
x
+
(
n
−
1
)
/
2
)
)
%
(
n
∣
1
)
y = f(x) = (2*((n|1)-x+(n-1)/2))\%(n|1)
y=f(x)=(2∗((n∣1)−x+(n−1)/2))%(n∣1)
3.代码
class Solution {
public:
void wiggleSort(vector<int>& nums) {
int n = nums.size();
std::nth_element(nums.begin(), nums.begin() + n/2, nums.end());
int mid = nums[n/2];
int i = 0;
int j = 0;
int k = n - 1;
#define a(i) nums[(2*((n|1)-i+(n-1)/2))%(n|1)]
while (j <= k) {
if (a(j) < mid) {
swap(a(i), a(j));
++i;
++j;
} else if (a(j) > mid) {
swap(a(j), a(k));
--k;
} else {
++j;
}
}
}
};
5.扩展
其实这个题目还有一种映射思路。
我们为了避免中位数相遇,做了前后两部分进行翻转。有办法不进行翻转吗?其实是有的。在找中位数的过程中,我们将大小关系翻转。
那么找到中位数之后,前面部分是大于等于中位数的。后面部分是小于等于中位数的。所以在奇数位上插入后部分的数,在偶数位上插入前半部分的数。这样会自然而然的避免了中位数的相遇(自己举个例子看下过程就知道了)
举个例子,对于:
4,5,5,6
翻转大小关系,将大于等于中位数的放左边,小于等于中位数的放右边:
6,5,5,4
因为中位数在前半部分的后面,在后面部分的前面,所以保证了中位数的尽量不相遇。
依次奇偶插入:
5,6,5,4
然后映射是:
0→1
1→3
2→0
3→2
映射关系是:
y = f ( x ) = ( 2 ∗ i + 1 ) % ( n ∣ 1 ) y = f(x) = (2*i+1) \% (n|1) y=f(x)=(2∗i+1)%(n∣1)
代码:
class Solution {
public:
void wiggleSort(vector<int>& nums) {
int n = nums.size();
std::nth_element(nums.begin(), nums.begin() + n/2, nums.end());
int mid = nums[n/2];
// 3-way-partion
int i = 0;
int j = 0;
int k = n - 1;
#define a(i) nums[(2*(i)+1)%(n|1)]
while (j <= k) {
if (a(j) > mid) { //注意这里:将大于中位数的放在了左边
swap(a(i), a(j));
++i;
++j;
} else if (a(j) < mid) {
swap(a(j), a(k));
--k;
} else {
++j;
}
}
}
};