【Leetcode】324. Wiggle Sort II 324. 摆动排序 II

1

解法

基本框架是要形成一个模式:
偶数位放较小数(前半),奇数位放较大数(后半),由于前半的最大和后半的最小有可能相等【它们一定都是中位数】,那么要把前半的最大和后半的最小分开,最好的方法就是分别放在两头

解法一:基于排序

首先得到排好序的数组,然后:

  • 偶数位递减放置前半,这样前半的最大在左边
  • 奇数位递减放置后半,这样后半的最小一定在右边

为什么不同时递增呢?这样不是就形成【前半的最大在左边,后半的最小在右边】的局面了吗?

  • 假如n=2k,偶数编号[0,2k-2],奇数编号[1,2k-1]
    • 如果取偶左奇右[0,2k-1],即前半倒过来放,前半的最大就在0,后半也倒过来放,后半的最小就在2k-1,它们只有在k=1时相邻,那么在保证能得到合法解的时候,如果k==1,那么相邻的这两个数一定不相等。
    • 相反,如果取偶右奇左[1,2k-2],即前半顺序放置,前半最大在2k-2,而后半也顺序放置,后半的最小在1,当k=1的时候虽然相邻,但是由于保证解合法所以没问题,但是在k=2的时候也相邻,这样就有可能出问题,比如在[4,5,5,6]这样的情况下。
  • 假如n=2k+1,偶数编号[0,2k],奇数编号[1,2k-1]
    • 如果取偶左奇右[0,2k-1],它们只有在k=1时相邻,那么在保证能得到合法解的时候,如果k==1,那么相邻的这两个数就是三个数里最大的两个,要得到合法解,它们必然不相等。
    • 相反,如果取偶右奇左[1,2k],同样当k=1的时候会相邻,通过上述分析可知没问题

综上所述,就是数组长度为4是两边都顺序摆会有问题,所以理论上把数组排序后前半和后半都倒序然后分别摆在偶数和奇数位即可。

class Solution(object):
    def wiggleSort(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        nums.sort()
        c = (len(nums)-1)//2+1
        nums[::2],nums[1::2] = nums[:c][::-1],nums[c:][::-1]

解法二:基于快排和三色排序

首先通过跟快排的partition相似的操作用 O ( n ) O(n) O(n)的方法找到中位数
然后,数组就分成了三部分:【大于中位数、小于中位数、等于中位数】。
跟上个解法里的分析相似,小于中位数的部分要放到偶数位索引较大的位置上,大于中位数的部分要放到奇数位的较小索引上,剩下的位置就放等于中位数的部分。
也就是说,假如我们按1,3,5,7,...,0,2,4,6,8....来遍历的话,整个数组会按【大于中位数、等于中位数、小于中位数】的顺序排列。
如何按照这个顺序遍历呢?
我们从0~n遍历,对应的(2*i+1)%nn就是这个顺序了,其中nn为大于等于n的最小奇数,当2*i+1不超过nn的时候遍历的是奇数,超过之后,由于除数也是奇数,取余之后作减法就是偶数了,所以达到按照上述顺序遍历的目的了。

然后回想一下三色排序的算法

维护0区域的下一个位置l,2区域的下一个位置r,以及当前遍历指针i

  • 如果a[i]==1,前进一位
  • 如果a[i]==0,将a[i]a[l]交换,由于0的个数增加了,所以l要增加;另外由于肯定有l<=i,那么换到a[i]位置上的a[l]的值肯定是1,所以i的值就直接增加就好
  • 如果a[i]==2,那么将a[i]a[r]交换,由于2的个数增加了,所以r要减小,换到a[i]上的数字有可能是0\1\2所以i就不增加了。
class Solution {
public:
    int n;
    void wiggleSort(vector<int>& a) {
        n = a.size();
        int tar = findkthnum(a,0,n-1,(n+1)>>1);
        // printf("%d\n",tar);
        int l=0,i =0,r = n-1;
        n |= 1;
        while (i<=r){
            int ii=idx(i), ll=idx(l), rr=idx(r);
            // printf("%d %d %d\n",ii,ll,rr);
            if (a[ii]<tar) {
                swap(a,ii,rr);
                // printf("%d %d %d %d\n",a[0],a[1],a[2],a[3]);
                r--;
            }
            else if (a[ii]>tar) {
                swap(a,ii,ll);
                // printf("%d %d %d %d\n",a[0],a[1],a[2],a[3]);
                i++,l++;
            }
            else i++;
        }
    }
    int idx(int a){
        return ((a<<1)+1)%n;
    }
    int findkthnum(vector<int>& a, int l, int r, int k){
        if (l==r) return a[l];
        int now=l;
        for(int i=l;i<r;i++) {
            if (a[i]<a[r]) swap(a,i,now++);
        }
        swap(a,r,now);
        if (k==now)
            return a[k];
        else if (k<now)
            return findkthnum(a,l,now-1,k);
        else
            return findkthnum(a,now+1,r,k);
    }
    
    void swap(vector<int>& a, int i,int j) {
        if (i!=j) {
            a[i]^=a[j];a[j]^=a[i];a[i]^=a[j];
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值