剑指offer76:数组中第k大的数字(快速排序)

题目:
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
简单回顾快速排序
原序列是[4,1,5,3,6,2,7,8]
直接上图片和代码
在这里插入图片描述
a图选取3作为中间值,将其交换到数的尾部,初始化p1指针到下标为-1的位置,指针p2至下标为0的位置,b图右移指针p2直到遇到第一个比3小的数字1,指针p1右移一格然后交换指针p1和p2指向的数字,c图同理,d图右移指针p2直到指向数字3,指向p1右移一格然后交换指针p1和p2指向的数字。
快速排序时间复杂度取决于所选取的中间值在数组中的位置,如果每次选取的中间值在排序数组中都接近于数组中间的位置,那么快速排序的时间复杂度是O(nlogn),如果每次选取的中间值都位于排序数组的头部或尾部,那么快速排序的时间复杂度为O(n^2)。在随机选取中间值的前提下,快速排序的平均时间复杂度为O(nlogn),是非常高效的排序算法。

import java.util.Random;

//快速排序 基本思想是分治法
public class SortArray02 {
    public int[] sortArray(int[] nums){
        quicksort(nums,0,nums.length-1);
        return nums;
    }

    public void quicksort(int[] nums, int start, int end) {
        if (end > start){
            int pivot = partition(nums,start,end);
            quicksort(nums,start,pivot-1);
            quicksort(nums,pivot+1,end);
        }
    }

    private int partition(int[] nums, int start, int end) {
        int random = new Random().nextInt(end - start+1)+start;
        swap(nums,random,end);
        int small = start-1;
        for (int i = start; i <end; i++) {
            if (nums[i] < nums[end]){
                small++;
                swap(nums,i,small);
            }
        }
        small++;
        swap(nums,small,end);
        return small;
    }
    private void swap(int[] nums,int index1,int index2){
        if (index1 !=index2){
            int temp = nums[index1];
            nums[index1] = nums[index2];
            nums[index2] = temp;
        }
    }
}

分析该题:
剑指offer第59题中介绍过一种基于最小堆的解法,详细请看该题,从数据流中读取n个数字并找出第k大的数字的时间复杂度是O(nlogk),空间复杂度O(k)。
59题中的数据位于一个数据流中,不能一次性将所有数据全部读入内存,而本题不一样,数据都保存在一个数组中,所有操作都在内存中完成,我们有更快找出第k大的数字的算法。
快速排序一次排序就能确定(固定)一个数字的顺序,在长度为n的排序数组中,第k大的数字的下标是n-k,可以利用快速排序的partition函数对数组进行分区,如果函数partition选取的中间值在分区之后的下标正好是n-k,分区后左边的值都比中间值小,右边的值都比中间值大,即使整个数组不是排序的,中间值也肯定是第k大的数字。
根据这种思路,我们来解这道题。
由于函数partition随机选择中间值,因此返回值也具有随机性,计算这种算法的时间复杂度需要概率相关知识,此处仅计算一个特定场合下的时间复杂度,假设每次选择的中间值都位于分区后的数组的中间位置,那么第一次函数扫描长度为n的数组,第二次函数扫描长度为n/2的子数组,第三次扫描长度为n/4的数组,重复该过程,直到子数组为1,因为n+n/2+n/4…+1=2n,因此总的时间复杂度为O(n)。
代码:

import java.util.Random;

public class FindKthLargest {
    public static void main(String[] args) {
        int[] nums = {3,1,2,4,5,5,6};
        int k = 3;
        FindKthLargest findKthLargest = new FindKthLargest();
        int kthLargest = findKthLargest.findKthLargest(nums, k);
        System.out.println(kthLargest);
    }
    public int findKthLargest(int[] nums, int k) {
        int target = nums.length - k;
        int start = 0;
        int end = nums.length - 1;
        int index = partition(nums, start, end);
        while (index != target) {
            if (index != target) {
                end = index - 1;
            } else {
                start = index + 1;
            }
            index = partition(nums, start, end);
        }
        return nums[index];
    }

    private int partition(int[] nums, int start, int end) {
        int random = new Random().nextInt((end - start)+1) + start;
        swap(nums, random, end);
        int small = start - 1;
        for (int i = start; i < end; i++) {
            if (nums[i] < nums[end]) {
                small++;
                swap(nums, i, small);
            }
        }
        small++;
        swap(nums, small, end);
        return small;
    }

    private void swap(int[] nums, int index1, int index2) {
        if (index1 != index2) {
            int temp = nums[index1];
            nums[index1] = nums[index2];
            nums[index2] = temp;
        }
    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

龙崎流河

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

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

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

打赏作者

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

抵扣说明:

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

余额充值