324. Wiggle Sort II

本文介绍了一种名为波浪排序(wiggle sort)的算法,该算法可以在O(n)时间内完成排序,实现nums[0]<nums[1]>nums[2]<nums[3]...的排序效果。文章详细解释了如何使用快速选择(quickselect)和三分法(three-way partition)来实现这一目标,并讨论了如何在有限的空间内完成排序。

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

Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3]….

Example:
(1) Given nums = [1, 5, 1, 1, 6, 4], one possible answer is [1, 4, 1, 5, 1, 6].
(2) Given nums = [1, 3, 2, 2, 3, 1], one possible answer is [2, 3, 1, 3, 1, 2].

Note:
You may assume all input has valid answer.

Follow Up:
Can you do it in O(n) time and/or in-place with O(1) extra space?

s思路:
1. 这道题一看,首先想到是用sort(),然后把后半部分和前半部分混和,例如:[1, 5, 1, 1, 6, 4],sort后得到[1,1,1,4,5,6],然后混合得到,[1,6,1,5,1,4].但这样的复杂度是o(nlgn)
2. 题目要求o(n), 仔细想,这道题之要求wiggle sort,只要有这个wave的效果,并不需要严格的排序。那就用quick select先找到中点,然后把和中点值相等的通过three way partition给gather到靠近中点的位置,最后得到的就左边值小的一个group,中间相等的一个group,右边值稍微大的一个group.三个group得到后,一样的进行混合,考虑到中间值可能重复的很多,在混合的时候,需要左右两个part都从最大的值来混合,保证wiggle sort的 nums[0] < nums[1] > nums[2] < nums[3]….性质,而不用担心出现相等的情况!
3. 总结一下过程:首先,quick select,把中间值放在(n-1)/2的坐标位置上,并且左边是小于等于这个值,右边是大于等于这个值,代码中就是findMedian()这个函数,回忆一下这个函数的过程:初始时,left=0,right=n-1,k=(n-1)/2,然后选用left的位置作为pivot,然后找这个值在排好序的序列中的位置,这个找的过程可以写成一个独立的子函数叫partition,就是用two pointer来比较确定这个pivot应该放的位置,例如:让i=left+1,j=right,然后i和left比较,j和left比较,最后希望是o(n)找到left正确的位置即可,然后swap。这个过程就是quick sort的思想;第二步,由于第一步quick select只是把找到中值的位置,并且让小于等于中值的数在左侧,大于等于的在右侧,这就有一个问题了,等于中值的可能分布在任意位置,必须把等于中值的放在一堆,才能保证nums[0] < nums[1] > nums[2] < nums[3]….,否则可能出现相等的情况,因此用threewaypartition用三个指针把数分堆,让等于中值的数集合在一起。这里回忆一下整个过程,写得很妙。
4. 接上文。因为要分三堆,也算是排序了,不过是很粗略的排序,用两个指针分别指向左右边界,然后用一个指针来遍历所有的数,一共就三个指针,这是在做之前就要明确的问题的本质和解题的规模。一共有两种设置指针的方式,如下图:图的上半部分是把left和right指针的初始位置分别放在序列的两端,这样做,left往右移动,right往左移动,最后是融合成一部分;图下半部分是另一种,把left和right初始位置放在了中值位置的左右,这么做,left则往左移动,right则往右移动,最后是分开的。所以在编写代码的时候,也发现第一种代码简单,但理解起来略困难,第二种则代码复杂一些,但理解起来很容易,提供两种思路,还是想说明:没有那一种就一定比另一种好,只是站在不同的位置看待同一个问题。
这里写图片描述
5. 继续描述。在找中值的时候,还可以利用c++的库函数nth_element(start iterator, mid iterator, end iterator)比自己写的都快!
6. 这道题,最后要求做到o(1)的空间复杂度,就不容易了,因为上面的方法还是需要o(n)的空间。参考https://discuss.leetcode.com/topic/32929/o-n-o-1-after-median-virtual-indexing 膜拜牛人一下,看得似懂非懂了!以后再思考!

class Solution {
public:

    int findMedian(vector<int>&nums,int left,int right,int k){
        //
        if(left==right) return right;
        int pivot=partition(nums,left,right);   
        if(pivot==k) return k;
        if(pivot<k) return findMedian(nums,pivot+1,right,k);//bug:k-pivot,不需要减,因为坐标绝对位置不变,k是绝对位置
        return findMedian(nums,left,pivot-1,k);

    }

    int partition(vector<int>&nums,int left,int right){
        //
        //int pivot=nums[left];
        int i=left+1,j=right;
        /*while(i<=j){
            while(i<=j&&nums[i]<=nums[left]) i++;
            while(i<=j&&nums[j]>nums[left]) j--;    
            if(i<j) swap(nums[i],nums[j]);
        }*/ 
        while(i<=j){
            if(nums[i]<=nums[left]) i++;
            else if(nums[j]>nums[left]) j--;
            else swap(nums[i],nums[j]);
        }
        swap(nums[j],nums[left]);
        return j;//
    }

    void threeWayPartition(vector<int>& nums,int k){
        //  
        int left=0,right=nums.size()-1;
        int i=0,val=nums[k];
        while(i<=right){
            if(nums[i]<val) swap(nums[i++],nums[left++]);
            else if(nums[i]>val) swap(nums[i],nums[right--]);
            else i++;   
        }

        /*int left=k-1,right=k+1;
        int i=0,val=nums[k];
        while(i<=left){
            if(nums[i]==val) 
                swap(nums[i],nums[left--]);
            else
                i++;
        }
        i=nums.size()-1;
        while(i>=right){
            if(nums[i]==val) 
                swap(nums[i],nums[right++]);
            else
                i--;
        }*/
    }

    void wiggleSort(vector<int>& nums) {
        //
        //step 1:quick select
        vector<int> res(nums);
        int n=nums.size();
        //findMedian(res,0,n-1,(n-1)/2);
        nth_element(res.begin(),res.begin()+(n-1)/2,res.end());//bug:nth_element(left,nth,right)
        threeWayPartition(res,(n-1)/2);
        int i=(n-1)/2,j=n-1,k=0;
        while(k<n){
            nums[k]=(k&1)?res[j--]:res[i--];
            k++;    
        }
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值