数组中的第K个最大元素
题目要求给出数组中第 k 大的元素,我最直接的想法就是对整个数组进行排序,然后返回数组中第 k 大的元素。
1. 冒泡排序
要想完成对数组 nums 的排序,冒泡排序需要进行 nums.size() - 1 轮排序,因为最后一个元素无需进行排序。冒泡排序每轮排序都会将待排数组中的最大值放到数组的末尾。
因此,要想找到数组中第 k 大的元素,只需进行 k 轮排序。
冒泡排序可以从小到大排序(正序),也可以从大到小排序(倒序)。
下面分别介绍正序和倒序实现。
1.1 从小到大的冒泡排序
如果采用从小到大的原则进行排序,nums.size() - 1 为数组最后的元素,也是数组中的最大值。那么第 k 大的元素在数组中的第 nums.size() - k 位上。
冒泡排序采用双层循环,内层循环为一趟排序,外层循环为 k 趟排序。第 k 趟排序后,数组索引 nums.size() - k 上为数组中第 k 大的元素。
算法的时间复杂度为 O(n²) ,还有很大的优化空间,因此可以考虑时间复杂度较低的快速排序。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
for(int last_position = nums.size() - 1; last_position >= nums.size() - k; last_position--)
{
// 设置标准位,优化最坏情况(整个数组都是逆序的)
int flag = 0;
for(int i = 0; i < last_position; i++)
{
if(nums[i] > nums[i + 1])
{
swap(nums[i], nums[i + 1]);
flag = 1;
}
}
// 如果一趟排序后,没有发生任何元素交换,说明数组已经是有序的,直接退出即可
if(flag == 0) break;
}
// 返回第 k 大的元素
return nums[nums.size() - k];
}
};
1.2 从大到小的冒泡排序
采用从大到小的原则进行排序,那么数组的最大值就在数组的首位,数组索引为 0 。因此,第 k 大的元素在数组中的索引为 k - 1 。最后返回 nums[k - 1] 。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
for(int last_position = nums.size() - 1; last_position >= k - 1; last_position--)
{
for(int j = 0; j < last_position; j++)
{
if(nums[j] < nums[j + 1])
{
swap(nums[j], nums[j + 1]);
}
}
}
return nums[k - 1];
}
};
2. 快速选择排序
快速选择排序是快速排序的升级版,快速排序需要递归 选取主元 左边的数组 和 选取主元 右边的数组。但是,我们的目的不是给整个数组排序,而是找到数组中第 k 大的元素,那么我们还需要同时递归主元的左右两侧的数组吗?
快速排序首先会选定主元,然后将主元放到数组中合适的位置,即主元左边的元素都小于主元,主元右边的元素都大于主元(采用从小到大排序)。一般用 partition 函数来完成放置主元的任务。调用 partition 函数后主元就被排好序了,之后不会再改变位置了。
利用这个特性,我们可以比较数组中第 k 大元素与主元位置间的关系。注意此处所说的数组中第 k 大元素指的是排好序后第 k 大元素所在的位置。
当二者相等时,说明主元就是数组中第 k 大元素,直接返回主元即可。
当主元位置大于数组中第 k 大元素时,说明当前的主元