wiggle sort和颜色分类

本文深入解析LeetCode 324题摆动排序二的解决方案,重点介绍如何通过找到中位数并使用巧妙的坐标映射技巧,实现数组元素的交错排序。详细解释了宏函数A的作用及其实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天做了摆动排序二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++;
            }
        }
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值