Bootstrap

寻找数组中的第K个最大元素

题目来自leetcode的215道题,寻找数组中的第K个最大值,如果要寻找一个数组的最大值,可能很简单,但是寻找第K个最大值,可能就不是那么简单了,当然这道题目有多个解法

第一种解法,排序法,想要找到第K大的元素,比较好的方法可能是排序法,经过排序的数组可以直接通过索引找到第K个值,这个值自然就是数组中第K大的值

在这里插入图片描述
在这个数组中,如果要寻找第2大的值,那么结果就是5
在这道题中,可以采用各种排序法,例如归并排序和快速排序等,很明显算法的时间复杂度是O(nlogn)
那么通过排序就能完成这道题目了

第二种解法,通过快速排序的思想,快速的完成这道题目,让时间复杂度降到O(n)级别的吧,性能更加的优秀。
快速排序的思想便是在第一遍排序过后,中间的值就是当前数组的中间值,左边都是小于它的值,右边则都是大于它的值
那么如果第一次排序后中间值的索引刚好等于k,那么代表已经找到这个值,直接返回中间值的数值即可。
但是如果中间值的索引大于k,那么代表k值在左边的区域,反之如果中间值的索引小于k,那么代表k值在右边的区域。需要注意这里的k索引并不是题目传进来的那一个k值,在算法开始的时候需要进行转换
有了这个信息后,就只需要寻找一边的元素就可以了,因为已经能确定k在哪一边。

代码如下

public int findKthLargest(int[] nums, int k) {
        //请注意这里的k是需要转换的,因为是取第k大的数,所以传过来的k并不是要查的k
        k = nums.length - k;
        int result = quickSort(nums, 0, nums.length - 1,k);
        return result;
    }
    private  int quickSort(int[] nums, int l, int r,int k) {
        //找到关键点p,此时索引p对应的值即是数组中的中间值
        int p = partition(nums,l,r);
        //递归终止条件,如果索引p等于k,那么代表索引p的值就是结果k值,那么直接返回索引p的值即可
        if(p==k) {
            return nums[p];
        }
        //如果p大于k,那么代表第k大的值在p的左边,此时左边的数组是未排序的,但能确定的是左边的值都比p的值要小
        int result = 0;
        if(p>k){
            result = quickSort(nums,l,p-1,k);
        }else {
            //如果p小于等于k,那么代表第k大的值在p的右边,此时右边的数组是未排序的,但能确定的是右边的值都比p的值要大
            result = quickSort(nums,p+1,r,k);
        }
        return result;
    }

    //返回值是下标p,p左边的元素都小于p的元素,p右边的元素都大于p的元素。试着用一次遍历,使用原地排序,不创建新的空间,来完成吧!
    public  int partition(int[] arr, int l, int r){
        // 生成 [l, r] 之间的随机索引
        int p = l + (new Random()).nextInt(r - l + 1);
        swap(arr, l, p);

        //记录开始的值
        int j = l;
        for (int i = l+1; i <= r; i++) {
            //System.out.println(arr[i]);
            if(arr[i]<=arr[l]){
                j++;
                //交换索引j和索引i的值
                swap(arr,i,j);
            }
        }
        //最后再将left元素与下标j元素交换就可以了
        swap(arr,l,j);
        return j;
    }

    public  void swap(int[] arr,int i,int j){
        int temp;
        temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
public static void main(String[] args) {
        int[] arr = {3,2,1,5,6,4};
        int k = 1;
        System.out.println(findKthLargest(arr,k));

        int[] arr2 = {3,2,3,1,2,4,5,5,6};
        int k2 = 4;
        System.out.println(findKthLargest(arr2,k2));
    }

在这里插入图片描述

时间复杂度O(n)是怎么得到的?
进行第一次快排后,需要扫描整个数组一次,就是对n个元素进行操作,到后面只需要操作一边就可以了,但是因为快速排序是随机算法,两边的元素可能不均等,因此通过期望可以大概得出每一边的元素为n/2
因此在最坏的情况下
需要的时间大约为n+n/2+n/4+…+1 ≈ 2n,因此是O(n)级别的算法。

当然partition也可以选择双路快速排序

private int partition(int[] arr, int l, int r){

        // 生成 [l, r] 之间的随机索引
        int p = l + (new Random()).nextInt(r - l + 1);
        swap(arr, l, p);

        // arr[l+1...i-1] <= v; arr[j+1...r] >= v
        int i = l + 1, j = r;
        while(true){

            while(i <= j && arr[i] < arr[l])
                i ++;

            while(j >= i && arr[j] > arr[l])
                j --;

            if(i >= j) break;

            swap(arr, i, j);

            i ++;
            j --;
        }

        swap(arr, l, j);
        return j;
    }
;