题目地址:力扣
这道题实际上就只是考察排序知识,重点是要注意一下题目里说的第k大的,如果从小到大排序那么取元素的时候就要从后往前取。同时题目中要求要以O(n)的复杂度来完成,但是主流的排序时间复杂度的下界普遍都是O(nlogn),因此我们需要采用一些方法来提前结束排序。
解法1:堆排序
思路:只要提到要第k大的元素,我们首先就应该想到堆排序。建好堆之后只需要做k-1次的交换与调整,那么做完之后第k大的元素就必然在堆的顶部。
class Solution {
public:
void adjust(vector<int> &nums, int i, int sz)
{
int lpos = 2*i+1, rpos = 2*i+2, largest = i;
if (lpos < sz && nums[lpos] > nums[i])
largest = lpos;
if (rpos < sz && nums[rpos] > nums[largest])
largest = rpos;
if (largest != i)
{
swap(nums[largest], nums[i]);
adjust(nums, largest, sz);
}
}
void build_heap_from_buttom(vector<int> &nums)
{
if (nums.size() != 1)
for (int i = nums.size()/2 -1; i >= 0; --i)
adjust(nums, i, nums.size());
}
void heap_sort(vector<int> &nums, int k)
{
build_heap_from_buttom(nums);
// 注意这里的i最大取到num.size()-k-1,此时做完交换和调整后,第k大的元素正好在堆顶
for(auto i = nums.size()-1; i > nums.size()-k; --i)
{
swap(nums[i], nums[0]);
adjust(nums, 0, i);
}
}
int findKthLargest(vector<int>& nums, int k) {
heap_sort(nums, k);
return nums[0];
}
};
解法2:快速排序
由于快排每一次确定一个元素的位置,因此我们只需要根据k是比当前排序元素大还是比排序元素小来对一侧进行快排,最终找到位于nums.size()-k的元素就是数组中第K大的元素
class Solution {
public:
void quick_sort(vector<int> &nums, vector<int>::iterator lp, vector<int>::iterator rp, int k)
{
if (rp - lp > 0)
{
auto olp = lp, orp = rp;
int random = rand() % (rp - lp + 1);
swap(*lp, *(lp + random));
int pivot = *lp;
while (rp != lp)
{
while (*rp >= pivot && lp != rp )
--rp;
while (*lp <= pivot && lp != rp)
++lp;
swap(*lp, *rp);
}
swap(*olp, *lp);
if (nums.end() - lp > k)
quick_sort(nums, rp+1, orp, k);
else if (nums.end() - lp < k)
quick_sort(nums, olp, lp-1, k);
}
}
int findKthLargest(vector<int>& nums, int k) {
quick_sort(nums, nums.begin(), nums.end()-1, k);
return nums[nums.size()-k];
}
};
解法3:计数排序
由于题目中给出数字的范围在-10000到10000之间,因此可以考虑采用计数排序的方法,这种方法虽然可能会占用一定的空间,但是时间复杂度通常来说较优
class Solution {
public:
void count_sort(vector<int> &nums)
{
// 定义一个map用来存数以及出现的次数
map<int, int> cnt_map;
// 遍历数组并将其添加进map
for (auto i : nums)
{
if (cnt_map.find(i) == cnt_map.end())
cnt_map[i] = 1;
else
++cnt_map[i];
}
// idx表示原数组的下标
int idx = 0;
// 遍历map将排序后的结果放回到原数组中
for (auto it = cnt_map.begin(); it != cnt_map.end(); ++it)
{
// 只要这个数出现次数不为0,那么就把原数组中当前位置改为这个数
while (it->second != 0)
{
nums[idx++] = it->first;
--it->second;
}
}
}
int findKthLargest(vector<int>& nums, int k) {
count_sort(nums);
return nums[nums.size()-k];
}
};
注意到上面使用了STL提供的map来进行排序,这种方法好的方面是当数组元素较少的时候可以节约空间,但是数组元素较多的时候可能排序会花费较多时间,因此可以采用自己开辟的空间来替代map,代码如下:
class Solution {
public:
void count_sort(vector<int> &nums)
{
// 开辟20002大小的空间用于存储从-10000到10000的数
vector<int> ivec(20002, 0);
// 注意对应下标的关系应该是值为i的数存在下标为i+10001的地方
for (auto i : nums)
++ivec[i+10001];
int idx = 0;
// 遍历ivec将元素倒回到nums数组中
for (int i = 0; i < ivec.size(); ++i)
{
while (ivec[i] != 0)
{
nums[idx++] = i-10001;
--ivec[i];
}
}
}
int findKthLargest(vector<int>& nums, int k) {
count_sort(nums);
return nums[nums.size()-k];
}
};
解法4:基数排序
class Solution {
public:
void radix_sort(vector<int> &nums)
{ // 开辟一个二维数组,第一维度为10,第二个维度为空
vector<vector<int>> ivec(10, vector<int>(0));
// 因为题目中最小的可能值为-10000,因此所有数加上10000必然是正数
for (auto &i : nums)
i += 10000;
// 比较的位数,1表示当前比较个位
int digit = 1;
// 除非所有的数都在0号桶中,否则循环继续
while (true)
{
// 根据当前位的情况把数塞进桶里
for(auto i : nums)
ivec[i%(digit*10)/digit].push_back(i);
// idx用于把桶里的数放回原数组中
int idx = 0;
// 若所有的数都在0号桶中,就结束循环
if (ivec[0].size() == nums.size())
break;
// 循环每个桶,将每个桶里的元素放回nums中,并且清空桶
for (int i = 0; i < 10; ++i)
{
for(int j = 0; j < ivec[i].size(); ++j)
nums[idx++] = ivec[i][j];
ivec[i].clear();
}
// 下一次比较更高位
digit *= 10;
}
// 最后需要把所有数都恢复原来的值
for (auto &i : nums)
i -= 10000;
}
int findKthLargest(vector<int>& nums, int k) {
radix_sort(nums);
return nums[nums.size()-k];
}
};
Accepted
- 39/39 cases passed (52 ms)
- Your runtime beats 68.95 % of cpp submissions
- Your memory usage beats 35.85 % of cpp submissions (44.4 MB