题目
在学习数据结构的过程,一定会遇到这样一道题目,海量数据中查找前k个最大/最小的数,LeetCode题目面试题40. 最小的k个数:输入整数数组 arr
,找出其中最小的 k
个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
解题思路
入门级
- 整体排序:直接使用C++ sort函数,sort是基于快排的优化
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> ans;
sort(arr.begin(), arr.end());
for (int i = 0; i < k; ++i)
ans.push_back(arr[i]);
return ans;
}
};
- 部分排序:直接进行k次遍历,找到前k个最小的数字,类似于简单选择排序(打扑克牌)
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
int len = arr.size();
int min, min_index;
vector<int> ans;
for (int i = 0; i < k; ++i)
{
min = arr[i];
min_index = i;
for (int j = i+1; j < len; ++j)
{
if (min > arr[j])
{
min = arr[j];
min_index = j;
}
}
ans.push_back(arr[min_index]);
arr[min_index] = arr[i];
}
return ans;
}
};
进阶
- 快排:借助快排的特点,每一趟快排都会找到一个确切的位置,小的数在左边,大的数在右边,所以只要快排找到第k个位置的数字即可,不用对整个数组进行多余的排序操作。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> ans;
if (k==0)
return ans;
quick_sort(arr, 0, arr.size()-1, k);
for (int i = 0; i < k; ++i)
ans.push_back(arr[i]);
return ans;
}
void quick_sort(vector<int> &arr, int low, int high, int k)
{
if (low < high)
{
int pivot_positon = partition(arr, low, high);
if (pivot_positon > k-1 )
quick_sort(arr, low, pivot_positon-1, k);
else if (pivot_positon < k -1)
quick_sort(arr, pivot_positon+1, high, k);
}
}
int partition(vector<int> &arr, int low, int high)
{
int pivot = arr[low];
while (low < high)
{
while (low<high && arr[high]>=pivot)//此处不要少了=,否则相同的数据会死循环
--high;
arr[low] = arr[high];
while (low<high && arr[low]<pivot)
++low;
arr[high] = arr[low];
}
arr[low] = pivot;
return low;
}
};
- 最小(大)堆:用堆作为数据结构来管理数据,堆是一棵完全二叉树,完全二叉树因为结构的特殊性,父节点的下标是子节点下标一半,所以通常用数组很好表示。堆排序分为构造堆和排序两个过程
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
if(k == 0) return vector<int>();
vector<int> max_heap_vec(arr.begin(), arr.begin()+k);
//对前k个数构造最大堆
buildMaxHeap(max_heap_vec);
//剩余的数插入堆里,堆顶部是最大值,出现比最大值还小的数时,替换掉堆顶,然后把堆顶元素向下调整,形成新的最大堆。
for(int i = k; i<arr.size(); ++i){
// 出现比堆顶元素小的值, 置换堆顶元素, 并调整堆
if(arr[i] < max_heap_vec[0])
{
max_heap_vec[0] = arr[i];
downAdjust(max_heap_vec, 0);
}
}
return max_heap_vec;
}
private:
void buildMaxHeap(vector<int>& v){
// 所有非叶子节点从后往前依次下沉
for(int i = static_cast<int>(v.size()-1) / 2; i>=0; --i) //最后一个叶结点的父节点开始,父节点向下调整,直到根节点调整完成
{
downAdjust(v, i);
}
}
void downAdjust(vector<int>& v, int index)
{
int parent = v[index];
// 左孩子节点索引,若有子节点,左孩子必然存在,完全二叉树特点
int child_index = 2*index;
while(child_index < v.size()){
// 判断是否存在右孩子, 并选出较大的节点
if(child_index+1 < v.size() && v[child_index+1] > v[child_index]){
++child_index;
}
// 判断父节点和子节点的大小关系
if(parent >= v[child_index])
break;
// 较大节点上浮
v[index] = v[child_index];
index = child_index;
child_index = 2*index;
}
v[index] = parent;
}
};