leetcode 912. Sort an Array(数组排序:quick sort, merge sort, 桶排序)

在这里插入图片描述

本题是典型的数组排序,要求时间复杂度在O(nlogn),
借此复习一下quick sort, merge sort和桶排序。

思路:

1.quick sort

quick sort的平均时间复杂度在O(n), 但是最坏的情况是O(n2),
它的主要思想是在数组中选一个pivot,
一般3种选法:选最左边的数字,选最右边的数字,随机数字。
下面的图是选最后一个数字作为pivot.

然后进行操作,目的是让所有比pivot小的数字都在pivot左边,
所有比pivot大的数字都在它的右边。

具体怎么操作呢,看下面的流程:
在这里插入图片描述

假设把比pivot小的数字放在 i 的位置, 刚开始 i 在-1的位置,
j 从左到右遍历,注意我们现在的目的是把比pivot小的数字都换到左边(i 的位置),
所以遇到arr[ j ] > pivot时不需要操作,继续往右走,
当遇到arr[ j ] <= pivot时,把arr[ j ]换到 i 的位置,i每次先往右移一格再换(因为刚开始是-1)。
这样,比pivot小的数字会慢慢地换到比它大的数字左边。
当 j 到达 pivot前面一个数字时,停止,这时i 和 它前面的数字都比pivot小,
i+1 ~ pivot前面一个数字都比pivot大,
现在把pivot换到 i+1的位置,就达到了目的(pivot前面的数字都比它小,后面的数字都比它大)。

这里就不贴这么详细的过程图了,有兴趣的看链接

这个操作数组的过程叫做partition, 算法流程见下图:
在这里插入图片描述
整个quick sort的流程如下
在这里插入图片描述
它是一个分而治之的过程,每次得到pivot的位置,
然后对left ~ pivot, pivot+1 ~ right的部分再quick sort, 直到无法再分为止。
在这里插入图片描述
随机选pivot会比固定选的效果好,平均时间复杂度更易达到O(n),
如果随机选数字,把随机选的数字换到最右边,剩下的步骤和上面一样。

本题选最右边的数字作为pivot出现了TLE, 换成随机选通过,但不是很快。

class Solution {
    Random random = new Random();
    public int[] sortArray(int[] nums) {
        quickSort(nums, 0, nums.length-1);
        return nums;
    }

    void quickSort(int[] nums, int l, int r) {
        if(l < r) {
            int pivot = partition(nums, l, r);
            quickSort(nums, l, pivot-1);
            quickSort(nums, pivot+1, r);
        }
    }

    int partition(int nums[], int l, int r) {     
        int pivot = random.nextInt(r+1-l) + l;  
        
        swap(nums, pivot, r);
        int low = l-1;
        for(int high = l; high < r; high++) {
            if(nums[high] <= nums[r]) {
                low ++;
                swap(nums, low, high);
            }
        }
        swap(nums, low+1, r);
        return (low+1);
    }

    void swap(int[] nums, int low, int high) {
        int tmp = nums[low];
        nums[low] = nums[high];
        nums[high] = tmp;
    }
}

2.merge sort

merge sort的时间复杂度在O(nlogn), 所以适合大规模数字排序的情况,
它常和其他排序算法(比如quick sort)结合起来使用,以提升整体的效率。

它是一个先分再合的过程,如下。
已知数组最左边下标是l, 最右边下标是r, 那么可以计算中间下标mid,
然后拆分成两部分:l ~ mid, mid+1 ~ r
一直到不能再拆分为止。

然后在把两两部分排序合起来,最后形成整个数组。
两两合起来的过程就是两个数组(分别copy出来)的下标都指向0,然后一个一个数字比较,把较小的先存起来(直接存到原数组),然后移动指针,比较下一个,直到两个数组都遍历完。

在这里插入图片描述
算法流程如下:

在这里插入图片描述

class Solution {
    public int[] sortArray(int[] nums) {
        mergeSort(nums, 0, nums.length-1);
        return nums;
    }

    void mergeSort(int[] nums, int l, int r) {
        if(l >= r) return;
        int mid = l + (r-l)/2;
        mergeSort(nums, l, mid);
        mergeSort(nums, mid+1, r);
        merge(nums, l, mid, r);
    }

    void merge(int[] nums, int l, int m, int r) {
        int leftLen = m - l + 1;
        int rightLen = r - m;
        int[] leftArr = new int[leftLen];
        int[] rightArr = new int[rightLen]; //mid+1 ~ r

        //把对应的数字copy进去
        for(int i = 0; i < leftLen; i++) leftArr[i] = nums[l+i];
        for(int i = 0; i < rightLen; i++) rightArr[i] = nums[m+1+i];

        int leftIdx = 0;
        int rightIdx = 0;
        int numsIdx = l;

        //sort & merge
        while(leftIdx < leftLen && rightIdx < rightLen) {
            if(leftArr[leftIdx] <= rightArr[rightIdx]) {
                nums[numsIdx] = leftArr[leftIdx];
                leftIdx ++;
            } else {
                nums[numsIdx] = rightArr[rightIdx];
                rightIdx ++;
            }
            numsIdx ++;
        }

        //把剩余的数字copy进去
        while(leftIdx < leftLen) {
            nums[numsIdx] = leftArr[leftIdx];
            leftIdx ++;
            numsIdx ++;
        }
        while(rightIdx < rightLen) {
            nums[numsIdx] = rightArr[rightIdx];
            rightIdx ++;
            numsIdx ++;
        }
    }
}

3.桶排序

桶排序,顾名思义就是每个数字对应一个桶,统计每个桶里面这个数字出现的次数。
然后从小到大把数字摆放进新的数组,每个数字摆放它出现的次数。
这种方法做到了O(n).

可以用hashMap统计每个数字出现的次数,这里直接用数组下标当作key,
但是数字范围太大,太浪费数组的空间,于是可以先找出最小值min 和 最大值 max,
数组的长度只需要max - min + 1即可。

最后不摆放进新的数组,而是直接摆放进原数组。

class Solution {
    public int[] sortArray(int[] nums) {
        int min = nums[0];
        int max = nums[0];
        
        for(int num: nums) {
            min = Math.min(num, min);
            max = Math.max(num, max);
        }
        int len = max - min + 1;
        int[] cnt = new int[len];
        for(int num : nums) {
            cnt[num-min]++;
        }
        
        int idx = 0;
        for(int i = 0; i < len; i++) {
            while(cnt[i] > 0) {
                nums[idx++] = min;
                cnt[i]--;
            }
            min++;
        }
        return nums;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝羽飞鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值