题目:来自脑客爱刷题
给定一个无序的整型数组arr,找到其中最小的k个数。
O(N*logK)的解法说起来非常简单,就是一直维护一个k个数的大根堆,这个堆代表目前选出的k个最小的数,在堆里的k个元素中堆顶的元素是最小的k个数里最大的那个。在遍历整个数组的过程中,看看当前数是否比堆顶元素小:如果是,就把堆顶的元素替换成当前的数,然后从堆顶的位置调整整个堆,让替换操作后的堆的最大元素继续处在堆顶的位置;如果不是,不进行任何的操作,继续遍历下一个数。在遍历完成后,堆中的k个数就是所有数组中最小的k个数。
方法二:BFPRT算法,时间复杂度O(N)
代码如下:
//在数组[begin,end]区间内,以数字part作为区分条件,左边的数比part小,中间的数是part(如果part不止一个,则连续出现),右边的数比part大
pair<int, int> partition(vector<int> &arr, int begin, int end, int part)
{
if (arr.empty() || begin > end || begin < 0 || end >= arr.size())
return pair<int, int>(-1, -1);
int smaller = begin - 1, bigger = end + 1,curindex=begin;
while (curindex < bigger)
{
if (arr[curindex] < part)
{
swap(arr[curindex], arr[smaller + 1]);
curindex++;
smaller++;
}
else if (arr[curindex] > part)
{
swap(arr[curindex], arr[bigger - 1]);
bigger--;
}
else
curindex++;
}
return pair<int, int>(smaller+1, bigger-1);
}
//getMinKhByBFPR2()函数相当于返回arr排序后,下标为k-1的数
int getMinKhByBFPR(vector<int> &arr, int begin, int end, int k)
{
int length = end - begin + 1;
if (begin == end)
{
return arr[begin];
}
int size = length % 5 == 0 ? length / 5 : length / 5 + 1;
vector<int> midnums(size);
for (int i = 0; i < size; i++)
{
int beginindex = begin + i * 5;
int endindex = min(beginindex + 4,end);
int len = endindex - beginindex + 1;
sort(arr.begin() + beginindex, arr.begin() + endindex + 1);
midnums[i] = arr[beginindex+len / 2];
}
int midnum = getMinKhByBFPR(midnums, 0, midnums.size() - 1, midnums.size() / 2 + 1);//返回的是下中位数
pair<int, int> r = partition(arr, begin, end, midnum);
if (k - 1 < r.first)
return getMinKhByBFPR(arr, begin, r.first - 1, k);
else if (k - 1 > r.second)
return getMinKhByBFPR(arr, r.second + 1, end, k);//注意,这里getMinKhByBFPR()的最后一个参数是k!
else
return midnum;
}
vector<int> getMinKNumsByBFPRT(const vector<int> &arr, int k)
{
vector<int> res;
if (arr.empty() || k <= 0)
return res;
if (k >= arr.size())
return arr;
vector<int> copyarr = arr;
int MinKh = getMinKhByBFPR(copyarr, 0, arr.size() - 1, k);
for (int i = 0; i < arr.size(); i++)
{
if (arr[i] < MinKh)
res.push_back(arr[i]);
}
while (res.size() < k)
res.push_back(MinKh);
return res;
}
注意,方法二使用的partition和传统快排使用的partition不同。上面的partition可以将数字part连续出现在中间。相当于把原始数组分成三部分:左边的数比part小,中间的数等于part,右边的数大于part。快排中使用这种partition可以更快地完成排序(因为它比传统方法进一步缩小了下一轮排序的区间)。另外,某些问题可以用这种partition更快解决。比如盒子里有红黑白三色球,把它们按颜色分开,就能用这种partition完成。
相关题型:
题目:EPI
该题的特殊之处在于,输入是一种流,事先不知道数组的长度,且其长度是非常大的。
建立一个数组存储读进来的数据,当数组长度达到2K-1的时候,可用partition的方式删除较小的k-1个数。
代码: