今天做了摆动排序二324。题目要求:
给定一个无序的数组 nums
,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]...
的顺序。
网上一个解答:
https://leetcode.com/problems/wiggle-sort-ii/discuss/77677/ono1-after-median-virtual-indexing
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 mid = *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 i = 0, j = 0, k = n - 1;
while (j <= k) {
if (A(j) > mid)
swap(A(i++), A(j++));
else if (A(j) < mid)
swap(A(j), A(k--));
else
j++;
}
}
看完后,一脸懵逼,A宏函数是干嘛??为啥这么操作。经过不断搜资料,才发现一点思路。
首先这个解法是这样的:
1)利用快排的partition找到中位数,时间复杂度O(n)
2)把小于中位数和大于中位数的交错排列,就得到正确的结果。
第一步容易实现,关键是第二步,这个操作是怎么回事?回想leetcode75题,颜色排序问题。也就是给定一个只含有0,1,2的数组,排序这个数组。回顾当时的解答:
void sortColors(vector<int>& nums) {
int r = 0, w = 0, b = nums.size() - 1;
while(w <= b){
if(nums[w] == 0)
swap(nums[w++], nums[r++]);
else if(nums[w] == 1)
w++;
else
swap(nums[b--], nums[w]);
}
}
是不是一模一样?也就是利用三个指针,把数字分成有序的三份的算法。再回到324wiggle sort,类比到这里的问题,其实也就是三个数字分别对应“小于median”,等于"median",大于"median"。
再观察A函数宏。
n|1做的操作是取大于等于n的最小的奇数,也就是对于奇数n,A(i) = (2i+1) % n,对于偶数n,A(i) = (2i+1) % (n+1),不难验证,其实它做的映射是:
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]
.
也就是通过下标,分别索引奇数位和偶数位。
和颜色分类那题不同,我们想要的不是排序,而是偶数位放小于median的数,奇数位放大于median的数,median都被放在了最后一位奇数位或者第一位偶数位。(为什么?因为median总是存在,最后从j++,j碰到k跳出循环)这个性质非常重要,这样就避免了4 5 5 6这种情况,把中位数放到最后或者第一位,就不会使得中间有相等的情况。
这个坐标映射非常巧妙,坐标映射通常非常有用,想起之前看到的那道题:一共100张牌,分别写着1,2,3...100,牌顶拿一张放在桌子上,再拿一张放在堆底,重复如此操作,知道手里没有牌,发现桌子上正好按照顺序1-100放着,问原来牌堆的顺序。
解法:
取1-100顺序的牌堆,按照如上规则,再发一次牌,得到的桌面上的顺序,每个牌面的值个坐标值交换,得到原来牌堆的顺序。
或者另一道:
对于1,2,3,...n,已经知道第q个全排列a1,..an,问倒数第q个全排列。
方法也是类似,
第q个全排列,相当于1 2 3 4...n经过变换编程a1 a2 a3... an
那么倒数第q个全排列,设为x1...xn,那么经过变换,可以得到x_a1, x_a2...x_an
也就是x_a1=n, x_a2=n-1....x_an=1,从而得到解。
记录一下自己的leetcode 324实现:
class Solution {
public:
int topk(vector<int> &nums, int l, int r, int k){
if(l >= r) return nums[l];
int i = l - 1;
int pivot = nums[r];
for(int j = l; j < r; j++){
if(nums[j] < pivot)
swap(nums[++i], nums[j]);
}
swap(nums[r], nums[++i]);
if(i == k){
return nums[i];
} else if(i < k){
return topk(nums, i+1, r, k);
} else{
return topk(nums, l, i-1, k);
}
}
void wiggleSort(vector<int>& nums) {
int n = nums.size();
if(n <= 1) return;
int mid = topk(nums, 0, n - 1, n / 2);
int i=0, j=0, k=n-1;
int oi, oj, ek;
n |= 1;
while(j <= k){
oi = (i + i + 1) % n;
oj = (j + j + 1) % n;
ek = (k + k + 1) % n;
if(nums[oj] < mid){
swap(nums[oj], nums[ek]);
k--;
} else if(nums[oj] > mid){
swap(nums[oi], nums[oj]);
i++;
j++;
} else {
j++;
}
}
}
};