虽然在 C++ 里可以通过 std::sort() 快速排序,而且刷题时很少需要自己手写排序算法,但是熟习各种排序算法可以加深自己对算法的基本理解,以及解出由这些排序算法引申出来的题目。
在排序算法中,常见的有:冒泡、插入、选择、归并和快速排序算法。
虽然在 C++ 里可以通过 std::sort() 快速排序,而且刷题时很少需要自己手写排序算法,但是熟习各种排序算法可以加深自己对算法的基本理解,以及解出由这些排序算法引申出来的题目。
在排序算法中,常见的有:冒泡、插入、选择、归并和快速排序算法。
(1)Kth Largest Element in an Array
题目描述
在一个未排序的数组中,找到第 k 大的数字。
本人思路
在做这道题的时候,最开始我思考了一下能不能用快排解决,但是当时对时间的估算有点问题,计算出来的时候是O(nlogn),导致转向了更复杂的冒泡算法。显然时间没法达到O(n)要求,超时了。看了题解之后,照着题解撸了一下快速排序,但由于没打乱,导致还是超时了。个人觉得堆排序来解决这种k-th Element比较靠谱(其实和冒泡的思路有一点像)。
快速选择
这是题解所给出的思路。快速选择一般用于求解 k-th Element 问题,可以在 O(n) 时间复杂度,O(1) 空间复杂度完成求解工作。快速选择的实现和快速排序相似,不过只需要找到第 k 大的枢(pivot)即可,不需要对其左右再进行排序。与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O(n²)。
// 辅函数 - 快速选择
int quickSelection(vector<int>& nums, int l, int r) {
int i = l + 1, j = r;
while (true) {
while (i < r && nums[i] <= nums[l]) {
++i;
}
while (l < j && nums[j] >= nums[l]) {
--j;
}
if (i >= j) {
break;
}
swap(nums[i], nums[j]);
}
swap(nums[l], nums[j]);
return j;
}
// 主函数
int findKthLargest(vector<int>& nums, int k) {
int l = 0, r = nums.size() - 1, target = nums.size() - k;
while (l < r) {
int mid = quickSelection(nums, l, r);
if (mid == target) {
return nums[mid];
}
if (mid < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return nums[l];
}
堆排序
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> minHeap;
for (int num : nums) {
minHeap.push(num);
if (minHeap.size() > k) {
minHeap.pop();
}
}
int result=minHeap.top();
return result;
}
(2)347. Top K Frequent Elements (Medium)
题目描述
给定一个数组,求前 k 个最频繁的数字。
个人思路
这个题目也可以用快排解决,但我现在个人更倾向于堆排序。所以这里给出堆排序的题解。
代码展示
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> freqMap;
for (int num : nums) {
freqMap[num]++;
}
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;
for (auto entry : freqMap) {
minHeap.push({entry.second, entry.first});
if (minHeap.size() > k) {
minHeap.pop();
}
}
vector<int> result;
while (!minHeap.empty()) {
result.push_back(minHeap.top().second);
minHeap.pop();
}
reverse(result.begin(), result.end()); // 将结果逆序,得到频率前 k 高的元素
return result;
}
总结
今天的主题虽然说的是排序,但其实都是同一类问题:k-th Element问题。对于这类问题,快排固然可以解决,但要考虑到最差情况和打乱排序的问题,个人还是更偏向于堆排序一点。
(1)Kth Largest Element in an Array
题目描述
在一个未排序的数组中,找到第 k 大的数字。
本人思路
在做这道题的时候,最开始我思考了一下能不能用快排解决,但是当时对时间的估算有点问题,计算出来的时候是O(nlogn),导致转向了更复杂的冒泡算法。显然时间没法达到O(n)要求,超时了。看了题解之后,照着题解撸了一下快速排序,但由于没打乱,导致还是超时了。个人觉得堆排序来解决这种k-th Element比较靠谱(其实和冒泡的思路有一点像)。
快速选择
这是题解所给出的思路。快速选择一般用于求解 k-th Element 问题,可以在 O(n) 时间复杂度,O(1) 空间复杂度完成求解工作。快速选择的实现和快速排序相似,不过只需要找到第 k 大的枢(pivot)即可,不需要对其左右再进行排序。与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O(n²)。
// 辅函数 - 快速选择
int quickSelection(vector<int>& nums, int l, int r) {
int i = l + 1, j = r;
while (true) {
while (i < r && nums[i] <= nums[l]) {
++i;
}
while (l < j && nums[j] >= nums[l]) {
--j;
}
if (i >= j) {
break;
}
swap(nums[i], nums[j]);
}
swap(nums[l], nums[j]);
return j;
}
// 主函数
int findKthLargest(vector<int>& nums, int k) {
int l = 0, r = nums.size() - 1, target = nums.size() - k;
while (l < r) {
int mid = quickSelection(nums, l, r);
if (mid == target) {
return nums[mid];
}
if (mid < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return nums[l];
}
堆排序
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> minHeap;
for (int num : nums) {
minHeap.push(num);
if (minHeap.size() > k) {
minHeap.pop();
}
}
int result=minHeap.top();
return result;
}
(2)347. Top K Frequent Elements (Medium)
题目描述
给定一个数组,求前 k 个最频繁的数字。
个人思路
这个题目也可以用快排解决,但我现在个人更倾向于堆排序。所以这里给出堆排序的题解。
代码展示
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> freqMap;
for (int num : nums) {
freqMap[num]++;
}
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;
for (auto entry : freqMap) {
minHeap.push({entry.second, entry.first});
if (minHeap.size() > k) {
minHeap.pop();
}
}
vector<int> result;
while (!minHeap.empty()) {
result.push_back(minHeap.top().second);
minHeap.pop();
}
reverse(result.begin(), result.end()); // 将结果逆序,得到频率前 k 高的元素
return result;
}
总结
今天的主题虽然说的是排序,但其实都是同一类问题:k-th Element问题。对于这类问题,快排固然可以解决,但要考虑到最差情况和打乱排序的问题,个人还是更偏向于堆排序一点。