题目要求给出数组中第 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 大元素时,说明当前的主元大于数组中第 k 大元素。因此可以从主元左边的数组中再选择一个元素作主元,并判断新的主元和数组中第 k 大元素之间的位置关系。
当主元位置小于数组中第 k 大元素时,说明当前的主元小于数组中第 k 大元素。因此可以从主元右边的数组中再选择一个元素作主元,并判断新的主元和数组中第 k 大元素之间的位置关系。
我们可以使用递归和非递归的方式实现快速选择排序,下面分别来介绍。
2.1 快速排序优化
一般情况下,我们默认选择数组的首元素为主元。
但是如果数组是逆序排列的话,那么算法将遇到最坏情况,时间复杂度为O(n²)。
因此我们需要选定一个更为合适的主元,可以用随机化的方式选择主元。
int idx = left + rand() % (right - left + 1);
swap(nums[idx], nums[left]);
int pivot = nums[left];
idx 为 [left,right] 之间的一个数,即数组中的一个数,但这个数是随机选取的。
每调用一次 partition 函数选取一次 idx,然后交换索引为 idx 和 left 的数组元素,
并将索引为 left 的数组元素赋给 pivot 主元。这样就实现了随机选取数组元素作为主元的目的。
2.2 快速选择排序(非递归)
从小到大排序的非递归实现
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int left = 0;
int right = nums.size() - 1;
int n = nums.size();
while(left < right)
{
int index = partition(nums, left, right);
if(index == n - k)
{
return nums[n - k];
}
else if(index < n - k)
{
left = index + 1;
}
else if(index > n - k)
{
right = index - 1;
}
}
return nums[n - k];
}
// 把主元放在合适的位置上
int partition(vector<int>& nums, int left, int right)
{
int idx = left + rand() % (right - left + 1);
swap(nums[idx], nums[left]);
int pivot = nums[left];
while(left < right)
{
while(left < right && nums[right] >= pivot)
{
right--;
}
nums[left] = nums[right];
while(left < right && nums[left] <= pivot)
{
left++;
}
nums[right] = nums[left];
}
nums[left] = pivot;
return left;
}
};
从大到小排序的非递归实现
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int left = 0;
int right = nums.size() - 1;
while(left < right)
{
int index = partition(nums, left, right);
if(index == k - 1)
return nums[k - 1];
else if(index > k - 1)
{
right = index - 1;
}
else if(index < k - 1)
{
left = index + 1;
}
}
return nums[k - 1];
}
int partition(vector<int>& nums, int left, int right)
{
int idx = left + rand() % (right - left + 1);
swap(nums[idx], nums[left]);
int pivot = nums[left];
while(left < right)
{
while(left < right && nums[right] <= pivot)
{
right--;
}
nums[left] = nums[right];
while(left < right && nums[left] >= pivot)
{
left++;
}
nums[right] = nums[left];
}
nums[left] = pivot;
return left;
}
};
2.3 快速选择排序(递归)
下面给出从小到大排序的递归实现。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
quicksort(nums, 0, n - 1, k);
return nums[n - k];
}
void quicksort(vector<int>& nums, int left, int right, int k)
{
int n = nums.size();
int index = partition(nums, left, right);
if(left < right)
{
if(index == n - k)
{
return;
}
else if(index < n - k)
{
quicksort(nums, index + 1, right, k);
}
else if(index > n - k)
{
quicksort(nums, left, index - 1, k);
}
}
return;
}
int partition(vector<int>& nums, int left, int right)
{
int idx = left + rand() % (right - left + 1);
swap(nums[idx], nums[left]);
int pivot = nums[left];
while(left < right)
{
while(left < right && nums[right] >= pivot)
{
right--;
}
nums[left] = nums[right];
while(left < right && nums[left] <= pivot)
{
left++;
}
nums[right] = nums[left];
}
nums[left] = pivot;
return left;
}
};
从大到小排序的递归实现
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int left = 0;
int right = nums.size() - 1;
return quicksort(nums, left, right, k);
}
int quicksort(vector<int>& nums, int left, int right, int k)
{
int index = partition(nums, left, right);
if(left < right)
{
if(index == k - 1)
return nums[k - 1];
else if(index > k - 1)
return quicksort(nums, left, index - 1, k);
else if(index < k - 1)
return quicksort(nums, index + 1, right, k);
}
return nums[k - 1];
}
int partition(vector<int>& nums, int left, int right)
{
int idx = left + rand() % (right - left + 1);
swap(nums[idx], nums[left]);
int pivot = nums[left];
while(left < right)
{
while(left < right && nums[right] <= pivot)
{
right--;
}
nums[left] = nums[right];
while(left < right && nums[left] >= pivot)
{
left++;
}
nums[right] = nums[left];
}
nums[left] = pivot;
return left;
}
};