LC-324. 摆动排序 II(贪心、快速选择+三数排序)

324. 摆动排序 II

难度中等512

给你一个整数数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的顺序。

你可以假设所有输入数组都可以得到满足题目要求的结果。

示例 1:

输入:nums = [1,5,1,1,6,4]
输出:[1,6,1,5,1,4]
解释:[1,4,1,5,1,6] 同样是符合题目要求的结果,可以被判题程序接受。

示例 2:

输入:nums = [1,3,2,2,3,1]
输出:[2,3,1,3,1,2]

提示:

  • 1 <= nums.length <= 5 * 104
  • 0 <= nums[i] <= 5000
  • 题目数据保证,对于给定的输入 nums ,总能产生满足题目要求的结果

**进阶:**你能用 O(n) 时间复杂度和 / 或原地 O(1) 额外空间来实现吗?

排序+贪心【O(NlogN) + O(n)】

class Solution {
    public void wiggleSort(int[] nums) {
        int[] sortnums = nums.clone();
        Arrays.sort(sortnums);
        int n = nums.length;
        int k = 0;
        for(int i = n%2==0 ? n-2 : n-1;  i >= 0; i -= 2){
            nums[i] = sortnums[k++];
        }
        for(int i = n%2==0 ? n-1 : n-2; i >= 0; i -= 2){
            nums[i] = sortnums[k++];
        }
    }
}

快速选择 + 三数排序

题解:https://leetcode.cn/problems/wiggle-sort-ii/solution/yi-bu-yi-bu-jiang-shi-jian-fu-za-du-cong-onlognjia/

我们发现,我们实际上并不关心A和B内部的元素顺序,只需要满足A和B长度相同(或相差1),且A中的元素小于等于B中的元素,且r出现在A的头部和B的尾部即可。

因此,我们第一步其实不需要进行排序,而只需要找到中位数即可。而寻找中位数可以用快速选择算法实现,时间复杂度为O(n)。

该算法与快速排序算法类似,在一次递归调用中,首先进行partition过程,即利用一个元素将原数组划分为两个子数组,然后将这一元素放在两个数组之间。两者区别在于快速排序接下来需要对左右两个子数组进行递归,而快速选择只需要对一侧子数组进行递归,所以快速选择的时间复杂度为O(n)。

找到中位数后,我们需要利用3-way-partition算法将中位数放在数组中部,同时将小于中位数的数放在左侧,大于中位数的数放在右侧。该算法与快速排序的partition过程也很类似,只需要在快速排序的partition过程的基础上,添加一个指针k用于定位大数

int i = 0, j = 0, k = nums.size() - 1;
while(j < k){
    if(nums[j] > mid){
        swap(nums[j], nums[k]);
        --k;
    }
    else if(nums[j] < mid){
        swap(nums[j], nums[i]);
        ++i;
        ++j;
    }
    else{
        ++j;
    }
}

在这一过程中,指针j和k从左右两侧同时出发相向而行,每次要么j移动一步,要么k移动一步,直到相遇为止。这一过程的时间复杂度显然为O(N)。

至此,原数组被分为3个部分,左侧为小于中位数的数,中间为中位数,右侧为大于中位数的数。之后的做法就与解法1相同了:我们只需要将数组从中间等分为2个部分,然后反序,穿插,即可得到最终结果。

java实现

class Solution {
    int[] nums;
    int n;
    int qselect(int l, int r, int k){
        if(l == r) return nums[l];
        int pivot = nums[l + r >> 1];
        int p1 = l - 1, p2 = r + 1;
        while(p1 < p2){
            do p1 ++; while(nums[p1] < pivot);
            do p2 --; while(nums[p2] > pivot);
            if(p1 < p2) swap(p1, p2);
        }
        int cnt = p2 - l + 1;
        if(cnt < k) return qselect(p2 + 1, r, k - cnt);
        else return qselect(l, p2, k);
    }

    void swap(int a, int b){
        int c = nums[a];
        nums[a] = nums[b];
        nums[b] = c;
    }
    public void wiggleSort(int[] _nums) {
        nums = _nums;
        n = nums.length;
        int p = qselect(0, n - 1, n + 1 >> 1);
        
        int p1 = 0, p2 = n - 1, loc = 0;
        while(loc <= p2){
            if(nums[loc] < p){
                swap(loc, p1);
                loc ++; p1 ++;
            } else if(nums[loc] > p){
                swap(loc, p2);
                p2 --;
            } else loc ++;
        }
        int[] clone = nums.clone();
        int idx = n - 1;
        for(int i = 1; i < n; i = i + 2){
            nums[i] = clone[idx];
            idx --;
        }
        for(int i = 0; i < n; i = i + 2){
            nums[i] = clone[idx];
            idx --;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值