算法笔记 -- 快速选择算法
2022-03-24 11:50:47
#
algorithm
这个算法的来源:215. 数组中的第K个最大元素。
今天在写leetcode的时候,写到了这题,也是发现了一个快排的新用途,可以使用快排的分治过程来寻找topK。
最初的思路是,对数组排序,然后取下标为n-k
即第k
大的元素,算法的思路也比较简单。
1 | class Solution { |
时间复杂度为\(O(nlogn)\),空间复杂度为\(O(1)\)。
另外,除了排序,还可以使用堆来优化,因为我们只需要求第K个,因此可以创造一个大顶堆,先将所有元素都添加进去,然后依次出队k
个即可。
1 | class Solution { |
后面发现,自己的这个解法还是不够优雅,其实不必将所有的元素都添加进堆中,只需要在堆中保存k
个元素。堆的话,我们选择小顶堆,因为这样取第k
个元素就只需要取队列的peek
即可。
遍历一趟nums
时,若堆中元素个数小于k
个时,直接入堆即可,当堆的元素个数大于k
时,判读此时的元素和堆顶的大小,如果当前堆顶元素比当前的入堆元素大,那么说明这个数显然不会是第k
个数及它后面的数,跳过即可,如果当前的元素比堆顶元素大,将堆顶出队,将当前元素入队,直到遍历完所有的,此时堆顶即时我们所求的第k
大的元素。
1 | class Solution { |
时间复杂度为\(O(nlogk)\),空间复杂度为\(O(k)\)。
接下里就是了解到的第三种算法,也是听过很多次的快速选择算法,可以做到在一个随机序列中,达到期望值为\(O(n)\)的时间复杂度。
快速选择算法的思路主要如下:
- 随机选择一个基准值
x
,遍历一趟序列,确保完成这个过程后,在x
左边的元素都\(\le x\),而在x
右边的元素都\(\ge x\); - 完成上述过程后,可以确保
x
已经在其排序的位置上,如果x
所处位置为所求位置k
,那么返回即可,否则,比较两边的元素数组,左边元素数量为当前x
所处位置 - 起始点,若\(leftLength < k\),则去右边找,否则继续在左边找,避免了快速排序中两次递归的过程;
1 | class Solution { |
时间复杂度为\(O(n)\),空间复杂度为\(O(logn)\)。
同样的,由于我们可以发现,其实递归的次数会比较小,在\(logn\)级别,因此可以直接使用while
循环替换即可。
1 | private int quickSelect(int[] nums, int l, int r, int k) { |
时间复杂度为\(O(n)\),空间复杂度为\(O(1)\)。