LeetCode 324. Wiggle Sort II (二分+三分 推荐)

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

Example 1:

Input: nums = [1, 5, 1, 1, 6, 4]
Output: One possible answer is [1, 4, 1, 5, 1, 6]

Example 2:

Input: nums = [1, 3, 2, 2, 3, 1]
Output: 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?

题目链接:https://leetcode.com/problems/wiggle-sort-ii/

题目分析:感觉follow up是可以归到hard一类的,discuss里发现的所有O(n)时间的解法无非两种

1. nth_element,它的作用是求第n大的元素,并把它放在第n位置上,做过kdtree的同学应该不陌生

2. 快速选择,基于快排的思想

可惜上面两种求取中位数的方式都不是严格意义上的O(n),下面说下我的做法

由于本题的输入均为32位整型,故可以通过二分的方式来求得中位数,目标是先找到一个最小的x使得小于等于它的数字个数大于等于n/2,然后再找原数组中大于等于且与x最接近的数,即为要求得的中位数,即便题目给出的数字范围更大,只要位数是确定的,复杂度都可看做nlog2(常数) => O(n)

求出中位数后不难想到按下标奇偶分开插数字,因为题目保证有解, 问题就变成了三部分的划分,大于中位数,小于中位数,等于中位数,类比经典的sort 3 colors问题,采用三分法,有一个贪心思想,即中位数尽可能分散,需对原下标做reindex(这里后来参考了StefanPochmann的解法,十分优美)。

题目应该是数据水了,下面的代码跑了7ms,然而最快的3ms代码尽然是时间nlogn,空间n的做法

class Solution {
    
    public int n;
    
    public void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
    
    public int getLess(int x, int[] nums) {
        int lessNum = 0;
        for (int i = 0; i < n; i++) {
            if (nums[i] <= x) {
                lessNum++;
            }
        }
        return lessNum;
    }
    
    public int getMid(int[] nums, int ma, int mi) {
        int l = mi, r = ma, mid = 0, ans = 0, lessNum = 0;
        while (l <= r) {
            mid = (l >> 1) + (r >> 1) + (l & r & 1);
            lessNum = getLess(mid, nums);
            if (lessNum <= (n >> 1)) {
                l = mid + 1;
            } else {
                r = mid - 1;
                ans = mid;
            }
        }
        return ans;
    }
    
    public int vi(int x) {
        return (x << 1 | 1) % (n | 1); 
    }
    
    public void wiggleSort(int[] nums) {
        n = nums.length;
        int ma = nums[0], mi = nums[0];
        for (int i = 0; i < n; i++) {
            ma = Math.max(ma, nums[i]);
            mi = Math.min(mi, nums[i]);
        }
        int tmid = getMid(nums, ma, mi);
        int mid = nums[0], minDiff = ma - mi;
        for (int i = 0; i < n; i++) {
            if (nums[i] >= tmid && nums[i] - tmid < minDiff) {
                minDiff = nums[i] - tmid;
                mid = nums[i];
            }
        }
        int l = 0, r = n - 1;
        for (int i = 0; i <= r; i++) {
            if (nums[vi(i)] > mid) {
                swap(nums, vi(i), vi(l));
                l++;
            } else if (nums[vi(i)] < mid) {
                swap(nums, vi(i), vi(r));
                i--;
                r--;
            }
        }
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值