题目地址:
https://www.lintcode.com/problem/wiggle-sort-ii/description
给定一个数组 A A A,要求重排数组,使得 A [ 0 ] < A [ 1 ] > A [ 2 ] < A [ 3 ] > . . . A[0]<A[1]>A[2]<A[3]>... A[0]<A[1]>A[2]<A[3]>...。题目保证答案存在。返回任意其中一个答案即可。
法1:排序。先对 A A A由小到大排序,然后开两个指针 i i i和 j j j, i i i指向 ( l A − 1 ) / 2 (l_A-1)/2 (lA−1)/2, j j j指向 l A − 1 l_A-1 lA−1。接着开一个新数组,然后按次序,把 i i i指向的数和 j j j指向的数分别填到新数组里即可。
算法正确性证明:
设
A
A
A已经排好序。接着用数学归纳法。当
l
A
=
1
,
2
l_A=1,2
lA=1,2的时候显然正确。当
l
A
≥
3
l_A\ge 3
lA≥3时,由于题目保证有解,所以
A
[
l
A
−
1
]
>
A
[
(
l
A
−
1
)
/
2
]
A[l_A-1]>A[(l_A-1)/2]
A[lA−1]>A[(lA−1)/2](否则的话
A
[
l
A
−
1
]
A[l_A-1]
A[lA−1]这个数,其出现次数大于数组总长度的一半,那么无论怎么排,一定存在两个相邻的位置恰好是
A
[
l
A
−
1
]
A[l_A-1]
A[lA−1],与题目保证有解矛盾),然后先把这两个数填起来,得
(
A
[
(
l
A
−
1
)
/
2
]
,
A
[
l
A
−
1
]
,
.
.
.
)
(A[(l_A-1)/2],A[l_A-1],...)
(A[(lA−1)/2],A[lA−1],...),填完这两个数之后,剩余的数组也是排好序的,由数学归纳法,存在一种排法满足要求,接着只需要证明
A
[
l
A
−
1
]
>
A
[
(
l
A
−
1
)
/
2
−
1
]
A[l_A-1]>A[(l_A-1)/2-1]
A[lA−1]>A[(lA−1)/2−1],这个也是显然的,否则的话
A
[
l
A
−
1
]
A[l_A-1]
A[lA−1]这个数也要出现超过一半。由数学归纳法,算法正确。
代码如下:
import java.util.Arrays;
public class Solution {
/*
* @param nums: A list of integers
* @return: nothing
*/
public void wiggleSort(int[] nums) {
// write your code here
Arrays.sort(nums);
int len = nums.length;
int[] tmp = new int[len];
int i = len - 1 >> 1, j = len - 1;
int idx = 0;
while (i >= 0 || j > len - 1 >> 1) {
tmp[idx++] = nums[i--];
// 这里要判一下是否已经填满了,否则有可能会出界
if (idx < len) {
tmp[idx++] = nums[j--];
}
}
// 再填回来
for (int k = 0; k < len; k++) {
nums[k] = tmp[k];
}
}
}
时间复杂度 O ( n log n ) O(n\log n) O(nlogn),空间 O ( n ) O(n) O(n)。
法2:快速选择。试想,如果我们求出了 A A A的中位数 m m m(这里的中位数定义为在 A A A排序的情况下,下标是 l A / 2 l_A/2 lA/2的数。如果数组长度为偶数,也可以取下标是 ( l A − 1 ) / 2 (l_A-1)/2 (lA−1)/2的数),我们就可以这样解决问题:开一个新数组 B B B,长度与 A A A一样,并把每个数都赋值为 m m m,接着将原数组 A A A里的不等于 m m m的数穿插进去。具体穿插需要考虑数组的长度,如果 l A l_A lA是偶数,那么需要的效果是 A [ 0 ] < A [ 1 ] > A [ 2 ] < . . . > A [ l A − 2 ] < A [ l A − 1 ] A[0]<A[1]>A[2]<...>A[l_A-2]<A[l_A-1] A[0]<A[1]>A[2]<...>A[lA−2]<A[lA−1],我们可以将比 m m m大的数填到 A [ 1 , 3 , 5 , . . . ] A[1,3,5,...] A[1,3,5,...],将比 m m m小的数填到 A [ l A − 2 , l A − 4 , . . . ] A[l_A-2,l_A-4,...] A[lA−2,lA−4,...],由于题目保证答案一定存在,所以 m m m出现的次数一定不能超过 l A / 2 l_A/2 lA/2,所以填数的次数会大于等于 l A / 2 l_A/2 lA/2,从而会把值为 m m m的数都隔开,会产生一个合法解;如果 l A l_A lA是奇数,那么需要的效果是 A [ 0 ] < A [ 1 ] > A [ 2 ] < . . . > A [ l A − 3 ] < A [ l A − 2 ] > A [ l A − 1 ] A[0]<A[1]>A[2]<...>A[l_A-3]<A[l_A-2]>A[l_A-1] A[0]<A[1]>A[2]<...>A[lA−3]<A[lA−2]>A[lA−1],我们可以将比 m m m大的数填到 A [ 1 , 3 , 5 , . . . ] A[1,3,5,...] A[1,3,5,...],将比 m m m小的数填到 A [ l A − 1 , l A − 3 , . . . ] A[l_A-1,l_A-3,...] A[lA−1,lA−3,...],由上面类似的论证,也可以得到一个合法解。这里得到合法解的方式是,让值为 m m m的数尽量分布在数组两端,以防它们相邻。而要求出中位数 m m m可以用快速选择算法得到。之所以要挑中位数来做,是因为这样可以保证在填数的时候不会“填出界”,因为大于和小于 m m m的数的个数不会超过 l A / 2 l_A/2 lA/2,这样就会把所有不等于 m m m的数都完全填进 B B B中,保证了最后所得的数组 B B B所含数字和 A A A是完全一样的。代码如下:
import java.util.Arrays;
public class Solution {
/*
* @param nums: A list of integers
* @return: nothing
*/
public void wiggleSort(int[] nums) {
// write your code here
int len = nums.length;
int mid = partition(nums, 0, len - 1, len >> 1);
int[] tmp = new int[len];
Arrays.fill(tmp, mid);
int l = 1, r = (len % 2 == 0) ? len - 2 : len - 1;
for (int i = 0; i < len; i++) {
if (nums[i] > mid) {
tmp[l] = nums[i];
l += 2;
} else if (nums[i] < mid) {
tmp[r] = nums[i];
r -= 2;
}
}
for (int i = 0; i < len; i++) {
nums[i] = tmp[i];
}
}
// 在nums[l, ..., r]中寻找排序后下标为rank的数
// (这里的意思是,想象一下将nums[l, ..., r]排好序并且下标从0计数,此时下标为rank的数)
private int partition(int[] nums, int l, int r, int rank) {
int left = l, right = r;
swap(nums, l, l + (r - l >> 1));
int piv = nums[left];
// 做partition,把比piv大于等于的数放右边,小于等于的数放左边
while (left < right) {
while (left < right && nums[right] >= piv) {
right--;
}
nums[left] = nums[right];
while (left < right && nums[left] <= piv) {
left++;
}
nums[right] = nums[left];
}
// 把分割点的数填回去
nums[left] = piv;
// 如果left - l < rank,说明要向右边找,并把左边的数去掉,所以下标要把排除掉的数的个数删掉;
// 如果left - l > rank,说明要向左边找,下标不用变;
// 如果left - l = rank,那么恰好找到了,返回之
if (left - l < rank) {
return partition(nums, left + 1, r, rank - (left - l + 1));
} else if (left - l > rank) {
return partition(nums, l, right - 1, rank);
} else {
return piv;
}
}
private void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
时空复杂度 O ( n ) O(n) O(n)(这里是平均时间复杂度)。