324. Wiggle Sort II

原题链接

Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3].... Follow Up: Can you do it in O(n) time and/or in-place with O(1) extra space?

这题最直观的做法是先把数组排成有序,再去做交换。具体的交换方法我没去研究,但理论上是一定可以在O(n)时间和O(1)空间排成大小交叉的形式。然而瓶颈在于最快的排序方法也无法做到O(n),所以这个方法行不通。 于是我放宽了限制,即使不把数组排序,如果能设法把数组的值分为两组,一组中的任意值不小于另一组的任意值,则我把较大组放在奇数位,较小组放在偶数位,也能满足题目要求。很明显两组的分界值可以用中位数(不是平均数),所以瓶颈在于找到中位数的复杂度。恰好C++提供了nth_element方法可以查找数组中第n大的值,而这个方法有O(n)时间复杂度和O(1)空间复杂度。

找到中位数后,数组划分为三类:大于中位数(L)、小于中位数(S),等于中位数(M)。显然,遍历数组时,S应该按顺序放到偶数位,L则按顺序放到奇数位,剩下的位置则放M。如果偶数位和奇数位都是按从前到后的顺序填充,那么对于两个S、两个L、两个M组成的数组,可能会形成SLSLMM的排列,也就是中位数堆积在末尾。因此,偶数位和奇数位应按相反的顺序填充,例如我的方法是奇数位从前到后,偶数位从后往前,这样就能排成SMSLML。

class Solution {
public:
    void wiggleSort(vector<int>& nums) {
        int n = nums.size();
        if(n<2) return;
        // Find a median
        auto midptr = nums.begin() + n/2;
        nth_element(nums.begin(), midptr, nums.end());// O(n) time, O(1) space
        int mid = *midptr;
        
        int j=1;// first odd
        int k=(n%2==0)?n-2:n-1;// last even
        int i=1;// start from first odd
        for(int count=0; count<n; count++) {
            if(nums[i]<mid) {
                swap(nums[i], nums[k]);
                k=k-2;
            }
            else if(nums[i]>mid) {
                swap(nums[i], nums[j]);
                j=j+2;
                i=i+2;
                if(i>n-1) i=0;// back to first even;
            }
            else {
                i=i+2;
                if(i>n-1) i=0;
            }
        }
    }
};

O(1)的交换值方法:从第一个奇数位开始遍历(用i标记),目标是把所有奇数位都变成不小于中位数的值。用k标记第一个S应存放的位置,j标记第一个L应存放的位置

1.如果当前奇数位的值比中位数小,则把它交换到k。如前文所述,k是从最后一个偶数位开始从后往前移动的,所以交换完后k-2。由于不知道交换回来的值是什么,所以当前值要再次和中位数比较,i不用移动。

2.如果当前奇数位的值等于中位数,先不动它,i向后移。

3..如果当前奇数位的值比中位数大,则把它交换到j。因为i>=j,j访问过的值i一定也访问过了,根据1、2的操作,被i访问过的值一定不会比中位值小,所以当前位交换回来的值一定不比中位数小。交换完后,i和j都向后移。实际上这个操作的目的就是把中位数移动到靠后的奇数位上。

最后遍历完奇数位后,可能还会出现LLLLSMSM,所以还要遍历一次偶数位。注意偶数位要从前往后遍历,与k的变化方向相反,这是为了避免把k访问过的位置再访问一遍(会导致移走正确的值)。

转载于:https://my.oschina.net/cofama/blog/961939

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值